Почему оператор switch не может применяться к строкам?

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

Причина, почему это связано с системой типов. C / C ++ не поддерживает строки как тип. Он поддерживает идею постоянного массива символов, но на самом деле он не полностью понимает понятие строки.

Чтобы сгенерировать код для оператора switch, компилятор должен понять, что означает, что для двух значений будет равным. Для таких элементов, как int и enums, это тривиальное сравнение бит. Но как компилятор сравнивает 2 строковых значения? Нечувствительны к регистру, нечувствительны, осведомлены о культуре и т. Д. Без полного понимания строки это невозможно точно ответить.

Кроме того, операторы switch C / C ++ обычно генерируются как таблицы ветвей . Не так легко создать таблицу ветвей для переключения стиля строки.

Как упоминалось ранее, компиляторы любят строить таблицы поиска, которые оптимизируют операторы switch до приближения O (1), когда это возможно. Объедините это с тем, что язык C ++ не имеет строкового типа. Строка std::string является частью стандартной библиотеки, которая не является частью языка как такового.

Я предложу альтернативу, которую вы, возможно, захотите рассмотреть, я использовал ее в прошлом для хорошего эффекта. Вместо переключения самой строки, переключите результат hash-функции, которая использует строку в качестве входных данных. Ваш код будет почти таким же понятным, как переключение строки, если вы используете предопределенный набор строк:

 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  #include  #include  // 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 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; } в #include  #include  #include  // 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 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; } 

Код написан Stefan Ruck 25 июля 2001 года.

Проблема заключается в том, что по соображениям оптимизации оператор switch в C ++ не работает ни на чем, кроме примитивных типов, и вы можете сравнивать их только с константами времени компиляции.

Предположительно, причина ограничения заключается в том, что компилятор может применить некоторую форму оптимизации, компилируя код до одной команды cmp и goto, где адрес вычисляется на основе значения аргумента во время выполнения. Поскольку разветвление и петли не очень хорошо сочетаются с современными процессорами, это может быть важной оптимизацией.

Чтобы обойти это, я боюсь, что вам придется прибегать к заявлениям.

Обновление C ++ 11, по-видимому, не @MarmouCorp выше, но http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

Использует две карты для преобразования между строками и перечислением classа (лучше, чем обычное перечисление, поскольку его значения ограничены внутри него и обратный поиск хороших сообщений об ошибках).

Использование статического кода в кодедегуруре возможно с поддержкой компилятора списков инициализаторов, что означает VS 2013 plus. gcc 4.8.1 было в порядке, не уверен, насколько дальше он будет совместим.

 ///  /// Enum for String values we want to switch on ///  enum class TestType { SetType, GetType }; ///  /// Map from strings to enum values ///  std::map MnCTest::s_mapStringToTestType = { { "setType", TestType::SetType }, { "getType", TestType::GetType } }; ///  /// Map from enum values to strings ///  std::map 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]); } 

std::map + C ++ 11 lambdas pattern без перечислений

unordered_map для потенциальной амортизации O(1) : Каков наилучший способ использования HashMap в C ++?

 #include  #include  #include  #include  #include  int main() { int result; const std::unordered_map> m{ {"one", [&](){ result = 1; }}, {"two", [&](){ result = 2; }}, {"three", [&](){ result = 3; }}, }; const auto end = m.end(); std::vector 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

Чтобы эффективно использовать этот шаблон внутри classов, инициализируйте карту lambda статически, иначе вы платите O(n) каждый раз, чтобы построить ее с нуля.

Здесь мы можем уйти от {} инициализации переменной static метода: Статические переменные в методах classа , но мы могли бы также использовать методы, описанные в: static constructors в C ++? Мне нужно инициализировать частные статические объекты

Необходимо было преобразовать захват lambda context [&] в аргумент, или это было бы undefined: const static auto lambda, используемый с захватом по ссылке

Пример, который производит тот же вывод, что и выше:

 #include  #include  #include  #include  #include  class RangeSwitch { public: void method(std::string key, int &result) { static const std::unordered_map> 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 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 для строк – я думаю, никто не думал, что это стоит, и я согласен с ними.

Почему нет? Вы можете использовать реализацию switch с эквивалентным синтаксисом и той же семантикой. Язык C не имеет объектов и объектов строк вообще, но строки в C являются строками с нулевым завершением, на которые ссылается указатель. Язык 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 или любые структуры или classы, которые поддерживают операции равенства (или comarisions для быстрого режима).

Особенности

  • любые типы данных, которые поддерживают сравнения или проверяют равенство
  • возможность создания каскадных вложенных коммутаторов statemens.
  • возможность взломать или оспаривать заявления о случаях
  • возможность использования нестандартных выражений case
  • можно включить быстрый статический / динамический режим с поиском дерева (для C ++ 11)

Различия в Sintax с языковым переключателем

  • заглавные слова
  • нужны скобки для выражения CASE
  • точка с запятой ‘;’ в конце заявлений не допускается
  • colon ‘:’ в CASE-заявлении не допускается
  • требуется одно из BREAK или FALL ключевого слова в конце выражения CASE

Для C++97 используется линейный поиск. Для C++11 и более современных можно использовать поиск в режиме быстрого поиска wuth, где оператор возврата в CASE становится недопустимым. Реализация языка C существует там, где используется тип char* и нулевые концевые сравнения строк.

Подробнее об этой реализации коммутатора.

В C ++ вы можете использовать оператор switch только для int и char

Я думаю, что причина в том, что в строках C нет примитивных типов, как сказал tomjen, думайте в строке как массив символов, поэтому вы не можете делать такие вещи, как:

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

C ++

constexpr hash-функция:

 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 ++ строки не являются первоclassными гражданами. Строковые операции выполняются через стандартную библиотеку. Думаю, вот в чем причина. Кроме того, C ++ использует оптимизацию таблицы ветвей для оптимизации операторов case switch. Посмотрите на ссылку.

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

Чтобы добавить вариацию с использованием самого простого контейнера (нет необходимости в упорядоченной карте) … Я бы не стал беспокоиться о перечислении – просто поставьте определение контейнера непосредственно перед коммутатором, чтобы было легко увидеть, какое число представляет в этом случае.

Это делает хешированный поиск в unordered_map и использует связанный int для управления оператором switch. Должно быть довольно быстро. Обратите внимание, что at используется вместо [] , поскольку я создал содержащую const . Использование [] может быть опасным – если строка отсутствует на карте, вы создадите новое сопоставление и можете в конечном итоге получить неопределенные результаты или постоянно растущую карту.

Обратите внимание, что функция at() генерирует исключение, если строка не находится на карте. Таким образом, вы можете сначала протестировать count() .

 const static std::unordered_map 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 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 } 

Вы не можете использовать строку в case switch. Только int & char разрешен. Вместо этого вы можете попробовать enum для представления строки и использовать ее в блоке case switch, например

 enum MyString(raj,taj,aaj); 

Используйте его в инструкции swich case.

Коммутаторы работают только с интегральными типами (int, char, bool и т. Д.). Почему бы не использовать карту для сопряжения строки с номером, а затем использовать этот номер с помощью переключателя?

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

во многих случаях вы можете запросить дополнительную работу, вытащив первый символ из строки и включив его. может закончиться тем, что нужно сделать вложенный ключ на charat (1), если ваши случаи начинаются с того же значения. любой, кто читает ваш код, по достоинству оценит намек, потому что большинство будет пытаться просто if-else-if

Это потому, что C ++ превращает ключи в таблицы перехода. Он выполняет тривиальную операцию над входными данными и переходит к соответствующему адресу без сравнения. Поскольку строка не является числом, а массивом чисел, C ++ не может создать таблицу перехода из нее.

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

(код из wikipedia https://en.wikipedia.org/wiki/Branch_table )

  • идея выбора переключателя / образца
  • Ограничения оператора C # - почему?
  • Оператор переключения Java несколько случаев
  • Как сделать заявление оператора C # использовать IgnoreCase
  • Оператор switch: по умолчанию должен быть последний случай?
  • Переключатель без перерыва
  • Java: если vs. Switch
  • Почему переключатель для enums принимает неявное преобразование в 0, но нет для какого-либо другого целого?
  • Операторы переключения .Net hashируются или индексируются?
  • Зачем нам нужно прерывать заявления о случаях?
  • Объявление переменной в операторе switch C #
  • Давайте будем гением компьютера.