Waarom kan de switch-instructie niet worden toegepast op strings?

Het compileren van de volgende code en kreeg de fout type illegal.

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

Je kunt string niet gebruiken in switchof case. Waarom? Is er een oplossing die goed werkt om logica te ondersteunen die vergelijkbaar is met het inschakelen van strings?


Antwoord 1, autoriteit 100%

De reden waarom heeft te maken met het typesysteem. C/C++ ondersteunt niet echt strings als type. Het ondersteunt het idee van een constante char-array, maar het begrijpt het idee van een string niet echt.

Om de code voor een switch-statement te genereren, moet de compiler begrijpen wat het betekent dat twee waarden gelijk zijn. Voor items als ints en enums is dit een triviale bitvergelijking. Maar hoe moet de compiler 2 stringwaarden vergelijken? Hoofdlettergevoelig, ongevoelig, cultuurbewust, etc … Zonder een volledig besef van een string kan dit niet nauwkeurig worden beantwoord.

Bovendien worden C/C++ switch-statements doorgaans gegenereerd als vertakkingstabellen. Het is lang niet zo eenvoudig om een vertakkingstabel te genereren voor een schakelaar in stringstijl.


Antwoord 2, autoriteit 30%

Zoals eerder vermeld, bouwen compilers graag opzoektabellen die de switch-statements optimaliseren naar timing van bijna O(1) waar mogelijk. Combineer dit met het feit dat de C++-taal geen stringtype heeft – std::stringmaakt deel uit van de standaardbibliotheek die niet per se deel uitmaakt van de taal.

Ik zal een alternatief aanbieden dat u misschien wilt overwegen, ik heb het in het verleden aan het goede effect gebruikt. In plaats van de reeks zelf over te schakelen, schakelt u het resultaat van een HASH-functie die de reeks als invoer gebruikt. Uw code is bijna net zo duidelijk als omschakeling van de tekenreeks als u een vooraf bepaalde reeks snaren gebruikt:

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

Er zijn een aantal voor de hand liggende optimalisaties die vrijwel volgen wat de C-compiler zou doen met een schakelafschrift … grappig hoe dat gebeurt.


Antwoord 3, Autoriteit 14%

C++

CONDEXPR HASH FUNCTION:

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
}

update:

Het bovenstaande voorbeeld is C++ 11. Daar constexprFunctie moet bij één instructie zijn. Dit was ontspannen in volgende C++ -versies.

In C++ 14 en C++ 17 kunt u het volgende HASH-functie gebruiken:

constexpr uint32_t hash(const char* data, size_t const size) noexcept{
    uint32_t hash = 5381;
    for(const char *c = data; c < data + size; ++c)
        hash = ((hash << 5) + hash) + (unsigned char) *c;
    return hash;
}

C++17 heeft ook std::string_view, dus je kunt het gebruiken in plaats van const char *.

In C++20 kun je proberen constevalte gebruiken.


Antwoord 4, autoriteit 8%

C++ 11 update van blijkbaar niet @MarmouCorp hierboven maar http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

Gebruikt twee kaarten om te converteren tussen de strings en de class enum (beter dan gewone enum omdat de waarden erin zijn opgenomen, en reverse lookup voor mooie foutmeldingen).

Het gebruik van static in de codeguru-code is mogelijk met compiler-ondersteuning voor initialisatielijsten, wat betekent VS 2013 plus. gcc 4.8.1 was in orde, niet zeker hoeveel verder terug het compatibel zou zijn.

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

Antwoord 5, autoriteit 6%

Het probleem is dat om redenen van optimalisatie de switch-instructie in C++ alleen werkt op primitieve typen, en je kunt ze alleen vergelijken met compileertijdconstanten.

Vermoedelijk is de reden voor de beperking dat de compiler een vorm van optimalisatie kan toepassen door de code te compileren tot één cmp-instructie en een goto waarbij het adres wordt berekend op basis van de waarde van het argument tijdens runtime. Aangezien vertakkingen en loops niet goed werken met moderne CPU’s, kan dit een belangrijke optimalisatie zijn.

Om dit te omzeilen, ben ik bang dat je gebruik moet maken van if-statements.


Antwoord 6, autoriteit 5%

std::map+ C++11 lambdas-patroon zonder opsommingen

unordered_mapvoor de mogelijk afgeschreven O(1): Wat is de beste manier om een HashMap in C++ te gebruiken?

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

Uitvoer:

one 1
two 2
three 3
foobar -1

Gebruik inside-methoden met static

Om dit patroon efficiënt in klassen te gebruiken, moet je de lambda-kaart statisch initialiseren, anders betaal je elke keer O(n)om het helemaal opnieuw te bouwen.

Hier kunnen we wegkomen met de {}initialisatie van een staticmethodevariabele: Statische variabelen in klassenmethoden, maar we kunnen ook de methoden gebruiken die worden beschreven op: statische constructors in C++? Ik moet persoonlijke statische objecten initialiseren

Het was nodig om de lambda-contextopname [&]om te zetten in een argument, anders zou dat niet gedefinieerd zijn: const-static-auto lambda gebruikt met capture by reference

Voorbeeld dat dezelfde uitvoer produceert als hierboven:

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

Antwoord 7, autoriteit 4%

Om een variatie toe te voegen met de eenvoudigste container die mogelijk is (geen geordende kaart nodig)… Ik zou me niet druk maken over een opsomming – plaats de containerdefinitie gewoon direct voor de schakelaar, zodat het gemakkelijk te zien is welk getal staat voor welk geval.

Dit doet een gehashte lookup in de unordered_mapen gebruikt de bijbehorende intom de switch-instructie aan te sturen. Zou vrij snel moeten zijn. Merk op dat atwordt gebruikt in plaats van [], aangezien ik die container constheb gemaakt. Het gebruik van []kan gevaarlijk zijn – als de string niet in de kaart staat, maak je een nieuwe kaart aan en krijg je mogelijk ongedefinieerde resultaten of een voortdurend groeiende kaart.

Houd er rekening mee dat de functie at()een uitzondering genereert als de tekenreeks niet op de kaart staat. Dus misschien wil je eerst testen met 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;
}

De versie met een test voor een ongedefinieerde string volgt:

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
// in C++20, you can replace .count with .contains
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
}

Antwoord 8, autoriteit 3%

In C++ en C werken schakelaars alleen op integer-types. Gebruik in plaats daarvan een if else-ladder. C++ had natuurlijk een soort swich-statement voor strings kunnen implementeren – ik denk dat niemand het de moeite waard vond, en ik ben het met ze eens.


Antwoord 9, autoriteit 3%

Waarom niet? U kunt switch-implementatiegebruiken met een gelijkwaardige syntaxis en dezelfde semantiek.
De taal Cheeft helemaal geen objecten en strings, maar
strings in Czijn null-beëindigde tekenreeksen waarnaar wordt verwezen door een aanwijzer.
De taal C++heeft de mogelijkheid om overbelastingsfuncties te maken voor:
objecten vergelijken of objecten gelijkheden controleren.
Zoals Cals C++is voldoende flexibel om zo’n schakelaar voor strings voor Cte hebben
taal en voor objecten van elk type die vergelijking of controle ondersteunen
gelijkheid voor C++taal. En moderne C++11staat toe om deze schakelaar te hebben
implementatie voldoende effectief.

Uw code ziet er als volgt uit:

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"

Het is mogelijk om meer gecompliceerde typen te gebruiken, bijvoorbeeld std::pairsof andere structs of klassen die gelijkheidsbewerkingen ondersteunen (of vergelijkingen voor de snelle-modus).

p>

Functies

  • elk type gegevens dat vergelijkingen ondersteunt of gelijkheid controleert
  • mogelijkheid om trapsgewijze geneste switch-statemens te bouwen.
  • mogelijkheid om casusverklaringen te doorbreken of te doorbreken
  • mogelijkheid om niet-constante hoofdletters en kleine letters te gebruiken
  • mogelijk om snelle statische/dynamische modus in te schakelen met zoeken in boomstructuur (voor C++11)

Sintax-verschillen met taalwisseling zijn

  • zoekwoorden in hoofdletters
  • haakjes nodig voor CASE-instructie
  • puntkomma ‘;’ aan het einde van uitspraken is niet toegestaan
  • dubbele punt ‘:’ bij CASE-instructie is niet toegestaan
  • een van de trefwoorden BREAK of FALL nodig hebben aan het einde van de CASE-instructie

Voor C++97gebruikte taal lineair zoeken.
Voor C++11en moderner is het mogelijk om de quick-modus te gebruiken met zoeken in de boomstructuur waarbij een return-instructie in CASE niet is toegestaan.
De Ctaalimplementatie bestaat waar char*type en nul-getermineerde tekenreeksvergelijkingen worden gebruikt.

Lees meer overdeze switch-implementatie.


Antwoord 10, autoriteit 2%

Ik denk dat de reden is dat strings in C geen primitieve typen zijn, zoals Tomjen zei, denk in een string als een char-array, dus je kunt geen dingen doen als:

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

Antwoord 11

In C++ snaren zijn geen eersteklas burgers. De tekenreekswerkzaamheden worden uitgevoerd via standaardbibliotheek. Ik denk, dat is de reden. Ook gebruikt C++ Branch Table-optimalisatie om de schakelaarcase-verklaringen te optimaliseren. Bekijk de link.

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


Antwoord 12

laat op het feest, hier is een oplossing die ik met een tijdje geleden kwam, wat volledig in de gevraagde syntaxis voldoet.

#include <uberswitch/uberswitch.hpp>
int main()
{
    uswitch (std::string("raj"))
    {
        ucase ("sda"): /* ... */ break;  //notice the parenthesis around the value.
    }
}

Hier is de code: https://github.com/falemagn/uberswitch


Antwoord 13

In C++ kunt u alleen een schakelinstructie gebruiken op INT en CHAR


Antwoord 14

U kunt schakelaar op snaren gebruiken.
Wat je nodig hebt, is een tabel met snaren, controleer elke string

char** strings[4] = {"Banana", "Watermelon", "Apple", "Orange"};
unsigned get_case_string(char* str, char** _strings, unsigned n)
{
    while(n)
    {
        n--
        if(strcmp(str, _strings[n]) == 0) return n;
    }
    return 0;
}
unsigned index = get_case_string("Banana", strings, 4);
switch(index)
{
    case 1: break;/*Found string `Banana`*/
    default: /*No string*/
}

Antwoord 15

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

Antwoord 16

in veel gevallen kun je extra werk doen door de eerste char uit de string te trekken en die aan te zetten. kan ertoe leiden dat u een geneste schakelaar op charat(1) moet uitvoeren als uw gevallen met dezelfde waarde beginnen. iedereen die je code leest, zou echter een hint op prijs stellen, omdat de meesten alleen als-anders-als zouden vragen


Antwoord 17

Meer functionele oplossing voor het switchprobleem:

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)
    {
        // ...
    }
}

Antwoord 18

Je zou de strings in een array kunnen plaatsen en een constexprgebruiken om ze tijdens het compileren naar indices te converteren.

constexpr const char* arr[] = { "bar", "foo" };
constexpr int index(const char* str) { /*...*/ }
do_something(std::string str)
{
    switch(quick_index(str))
    {
        case index("bar"):
            // ...
            break;
        case index("foo"):
            // ...
            break;
        case -1:
        default:
            // ...
            break;
    }

Voor quick_index, wat niet constexprhoeft te zijn, kunt u b.v. gebruik een unordered_mapom het O(1) tijdens runtime te doen. (Of sorteer de array en gebruik binair zoeken, zie hier voor een voorbeeld.)

Hier is een volledig voorbeeld voor C++11, met een eenvoudige aangepaste constexprtekenreeksvergelijker. Dubbele gevallen en gevallen die niet in de array staan (indexgeeft -1) worden gedetecteerd tijdens het compileren. Ontbrekende gevallen worden uiteraard niet gedetecteerd. Latere C++-versies hebben flexibelere constexpr-expressies, waardoor eenvoudigere code mogelijk is.

#include <iostream>
#include <algorithm>
#include <unordered_map>
constexpr const char* arr[] = { "bar", "foo", "foobar" };
constexpr int cmp(const char* str1, const char* str2)
{
    return *str1 == *str2 && (!*str1 || cmp(str1+1, str2+1));
}
constexpr int index(const char* str, int pos=0)
{
    return pos == sizeof(arr)/sizeof(arr[0]) ? -1 : cmp(str, arr[pos]) ? pos : index(str,pos+1);
}
int main()
{
    // initialize hash table once
    std::unordered_map<std::string,int> lookup;
    int i = 0;
    for(auto s : arr) lookup[s] = i++;
    auto quick_index = [&](std::string& s)
        { auto it = lookup.find(s); return it == lookup.end() ? -1 : it->second; };
    // usage in code
    std::string str = "bar";
    switch(quick_index(str))
    {
        case index("bar"):
            std::cout << "bartender" << std::endl;
            break;
        case index("foo"):
            std::cout << "fighter" << std::endl;
            break;
        case index("foobar"):
            std::cout << "fighter bartender" << std::endl;
            break;
        case -1:
        default:
            std::cout << "moo" << std::endl;
            break;
    }
}

Antwoord 19

U kunt geen string in switch-case gebruiken.Only int & amp; Char zijn toegestaan. In plaats daarvan kunt u ENUM proberen voor het representeren van de tekenreeks en gebruikt u deze in het Switch Case-blok zoals

enum MyString(raj,taj,aaj);

Gebruik het int de case-statement van Swich.


Antwoord 20

Schakelt alleen werk met integrale typen (INT, CHAR, BOOL, enz.). Waarom niet een kaart gebruiken om een ​​tekenreeks met een nummer te koppelen en vervolgens dat nummer met de schakelaar te gebruiken?


Antwoord 21

Dat komt omdat C++ wordt omschakelt in springtafels. Het voert een triviale werking op de invoergegevens en springt naar het juiste adres zonder te vergelijken. Omdat een string geen nummer is, maar een scala aan cijfers, C++ kan er geen springtabel van maken.

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 van wikipedia https://en.wikipedia.org/wiki/Branch_table)

Other episodes