Compiler le code suivant et avoir l'erreur de type illegal
.
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
Vous ne pouvez pas utiliser de chaîne dans switch
ou case
. Pourquoi? Existe-t-il une solution qui fonctionne correctement pour prendre en charge une logique similaire à celle consistant à activer des chaînes?
La raison en est liée au système de types. C/C++ ne supporte pas vraiment les chaînes en tant que type. Cela prend en charge l'idée d'un tableau de caractères constant mais ne comprend pas vraiment complètement la notion de chaîne.
Pour générer le code d'une instruction switch, le compilateur doit comprendre ce que cela signifie pour deux valeurs égales. Pour des éléments tels que ints et enums, il s'agit d'une comparaison de bits triviale. Mais comment le compilateur doit-il comparer 2 valeurs de chaîne? Sensible à la casse, insensible, conscient de la culture, etc.
En outre, les instructions de commutateur C/C++ sont généralement générées sous la forme/ tables de branche . Il n’est pas aussi facile de générer une table de branchement pour un commutateur de style chaîne.
Comme mentionné précédemment, les compilateurs aiment créer des tables de consultation qui optimisent les instructions switch
à un temps proche de O(1), dans la mesure du possible. Combinez cela avec le fait que le langage C++ n'a pas de type de chaîne - std::string
fait partie de la bibliothèque standard et ne fait pas partie du langage en soi.
Je vais proposer une alternative que vous voudrez peut-être envisager, je l'ai déjà utilisée à bon escient. Au lieu de basculer sur la chaîne elle-même, basculez le résultat d'une fonction de hachage qui utilise la chaîne en tant qu'entrée. Votre code sera presque aussi clair que le changement de chaîne si vous utilisez un ensemble prédéterminé de chaînes:
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:
...
}
}
Il y a un tas d'optimisations évidentes qui suivent à peu près ce que le compilateur C ferait avec une instruction switch ... drôle comment cela se produit.
Vous ne pouvez utiliser que des primitives telles que int, char et enum. La solution la plus simple pour le faire, comme vous voulez, est d'utiliser un 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;
}
Code écrit de Stefan Ruck le 25 juillet 2001.
Mise à jour C++ 11 apparemment pas de @MarmouCorp ci-dessus mais http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-Son-Strings-in-C.htm
Utilise deux mappes pour convertir entre les chaînes et l'énumération de classe (mieux qu'une énumération car ses valeurs sont étendues à l'intérieur de celle-ci et inverser la recherche des messages d'erreur Nice).
L'utilisation de static dans le code codeguru est possible avec la prise en charge par le compilateur des listes d'initialisateurs, qui signifie VS 2013 plus. gcc 4.8.1 était acceptable, pas sûr de la compatibilité avec le précédent.
/// <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]);
}
Le problème est que, pour des raisons d'optimisation, l'instruction switch en C++ ne fonctionne que sur les types primitifs et que vous ne pouvez les comparer qu'avec des constantes de temps de compilation.
On peut supposer que la raison de la restriction est que le compilateur est capable d'appliquer une forme d'optimisation en compilant le code en une instruction cmp et un goto où l'adresse est calculée en fonction de la valeur de l'argument au moment de l'exécution. Comme les branches et les boucles ne fonctionnent pas bien avec les processeurs modernes, cela peut constituer une optimisation importante.
Pour éviter cela, je crains que vous n’ayez à recourir à des déclarations.
std::map
+ C++ 11 modèle lambdas sans enums
unordered_map
pour la O(1)
____fonctionnement__: Quelle est la meilleure façon d'utiliser un HashMap en C++?
#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;
}
}
Sortie:
one 1
two 2
three 3
foobar -1
Utilisation dans les méthodes avec static
Pour utiliser efficacement ce modèle à l’intérieur des classes, initialisez la mappe lambda statiquement, sinon vous payerez O(n)
à chaque fois pour le construire à partir de zéro.
Ici, nous pouvons nous échapper avec l'initialisation {}
d'une variable de méthode static
: Variables statiques dans les méthodes de classe , mais nous pourrions également utiliser les méthodes décrites dans: constructeurs statiques en C++? J'ai besoin d'initialiser des objets statiques privés
Il était nécessaire de transformer la capture de contexte lambda [&]
en un argument, ou cela aurait été indéfini: const statique lambda automatique utilisé avec capture par référence
Exemple produisant le même résultat que ci-dessus:
#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++
fonction de hachage 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
}
En C++ et C, les commutateurs ne fonctionnent que sur les types entiers. Utilisez plutôt une échelle if else. C++ aurait évidemment pu implémenter une sorte d’instruction swich pour les chaînes - je suppose que personne n’a pensé que cela en valait la peine, et je suis d’accord avec elles.
Pourquoi pas? Vous pouvez utiliser switch implémentation avec une syntaxe équivalente et la même sémantique . Le langage C
ne comporte aucun objet ni objet de chaîne, mais Les chaînes dans C
sont des chaînes terminées par null référencées par le pointeur . Le langage C++
offre la possibilité de créer des fonctions de surcharge pour la comparaison des objets .__ ou la vérification de l’égalité des objets . As C
en tant que C++
est suffisamment flexible pour permettre un tel basculement des chaînes pour le langage C
Et pour les objets de tout type prenant en charge comparaison ou vérification. égalité pour C++
langue. Et les C++11
modernes permettent d’avoir cette implémentation de commutateur Suffisamment efficace.
Votre code sera comme ça:
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"
Il est possible d'utiliser des types plus compliqués, par exemple std::pairs
, ou des structures ou des classes prenant en charge les opérations d'égalité (ou des commandes pour le mode quick).
Différences Sintax avec changement de langue est
Pour C++97
langue utilisée recherche linéaire . Pour C++11
et plus moderne possible d'utiliser le mode quick
recherche d'arborescence en mode où l'instruction return dans CASE devenant non autorisée . L'implémentation du langage C
existe où le type char*
et zéro des comparaisons de chaînes terminées sont utilisées.
Lisez plus sur cette implémentation de commutateur.
Je pense que la raison en est que, dans C, les chaînes ne sont pas des types primitifs, comme dit tomjen, pense que dans une chaîne de caractères, vous ne pouvez pas faire les choses suivantes:
switch (char[]) { // ...
switch (int[]) { // ...
Pour ajouter une variante en utilisant le conteneur le plus simple possible (pas besoin de carte ordonnée) ... Je ne voudrais pas m'embêter avec une énumération - il suffit de mettre la définition du conteneur juste avant le commutateur pour pouvoir voir facilement quel nombre représente quel cas.
Cela effectue une recherche hachée dans le unordered_map
et utilise la variable int
associée pour piloter l'instruction switch. Devrait être assez rapide. Notez que at
est utilisé à la place de []
, comme je l'ai fait avec const
. Utiliser []
peut être dangereux - si la chaîne ne figure pas dans la carte, vous créez une nouvelle correspondance et vous pouvez vous retrouver avec des résultats non définis ou une carte en croissance constante.
Notez que la fonction at()
lève une exception si la chaîne n'est pas dans la carte. Donc, vous voudrez peut-être d'abord tester avec 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;
}
La version avec un test pour une chaîne non définie est la suivante:
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
}
En c ++, les chaînes ne sont pas des citoyens de première classe. Les opérations sur les chaînes se font via la bibliothèque standard. Je pense que c'est la raison. En outre, C++ utilise l'optimisation de table de branche pour optimiser les instructions de casse de commutateur. Regardez le lien.
En C++, vous ne pouvez utiliser qu'une instruction switch sur int et char
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;
}
dans de nombreux cas, vous pouvez obtenir un travail supplémentaire en extrayant le premier caractère de la chaîne et en l'activant. peut avoir à faire un commutateur imbriqué sur charat (1) si votre cas commence par la même valeur. Quiconque lirait votre code apprécierait un indice cependant, car la plupart des prob seraient simplement si-sinon-si
Solution de contournement plus fonctionnelle au problème de commutation:
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)
{
// ...
}
}
Les commutateurs ne fonctionnent qu'avec les types intégraux (int, char, bool, etc.). Pourquoi ne pas utiliser une carte pour associer une chaîne à un nombre, puis utiliser ce nombre avec le commutateur?
Vous ne pouvez pas utiliser de chaîne dans le casse switch.Only int & char sont autorisés. À la place, vous pouvez essayer enum pour représenter la chaîne et l’utiliser dans le bloc de casse comme
enum MyString(raj,taj,aaj);
Utilisez-le dans la déclaration de cas swich.