Почему оператор switch не может применяться к строкам?
Компилируя следующий код и получил ошибку type illegal
.
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
Вы не можете использовать строку ни в switch
ни в case
. Зачем? Есть ли какое-нибудь решение, которое хорошо работает для поддержки логики, похожей на включение строк?
Ответы
Ответ 1
Причина, связанная с системой типов. C/С++ не поддерживает строки как тип. Он поддерживает идею постоянного массива char, но на самом деле он не совсем понимает понятие строки.
Чтобы сгенерировать код для оператора switch, компилятор должен понимать, что означает, что для двух значений равны. Для таких элементов, как int и enums, это тривиальное сравнение бит. Но как компилятор сравнивает 2 строковых значения? Нечувствительны к регистру, нечувствительны, осведомлены о культуре и т.д. Без полного понимания строки это невозможно точно ответить.
Кроме того, операторы switch C/С++ обычно генерируются как таблицы ветвей. Не так легко создать таблицу ветвей для переключателя стиля строки.
Ответ 2
Как упоминалось ранее, компиляторы любят строить таблицы поиска, которые оптимизируют операторы switch
до ближайшего времени O (1), когда это возможно. Объедините это с тем, что язык С++ не имеет строкового типа. 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... забавно, как это происходит.
Ответ 3
Вы можете использовать только примитив включения, такой как int, char и перечисление. Самое простое решение сделать это, как вы хотите, это использовать перечисление.
#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 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 года.
Ответ 4
Обновление С++ 11, по-видимому, не @MarmouCorp выше, но http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm
Использует две карты для преобразования между строками и перечислением класса (лучше, чем обычное перечисление, поскольку его значения ограничены внутри него и обратный поиск хороших сообщений об ошибках).
Использование статики в кодедегуруре возможно с поддержкой компилятора для списков инициализаторов, что означает 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]);
}
Ответ 5
Проблема заключается в том, что по соображениям оптимизации оператор switch в С++ не работает ни на чем, кроме примитивных типов, и вы можете сравнивать их только с константами времени компиляции.
Предположительно, причина ограничения заключается в том, что компилятор может применить некоторую форму оптимизации, скомпилировав код до одной команды cmp и goto, где адрес вычисляется на основе значения аргумента во время выполнения. Поскольку ветвление и петли не очень хорошо сочетаются с современными процессорами, это может быть важной оптимизацией.
Чтобы обойти это, я боюсь, вам придётся прибегать к заявлениям.
Ответ 6
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
}
Ответ 7
std::map
+ С++ 11 lambdas pattern без перечислений
unordered_map
для потенциального амортизированного O(1)
: Каков наилучший способ использования 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
: Статические переменные в методах класса, но мы могли бы также использовать описанные методы at: статические конструкторы в С++? Мне нужно инициализировать частные статические объекты
Необходимо было преобразовать захват lambda context [&]
в аргумент, или это было бы undefined: const static auto lambda, используемое с захватом по ссылке
Пример, который производит тот же вывод, что и выше:
#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;
}
}
Ответ 8
В С++ и C коммутаторы работают только с целыми типами. Вместо этого используйте лестницу if else. Очевидно, что С++ реализовал какую-то инструкцию swich для строк - я думаю, никто не думал, что это стоит, и я согласен с ними.
Ответ 9
Почему бы и нет? Вы можете использовать реализацию коммутатора с эквивалентным синтаксисом и той же семантикой.
Язык 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
, или любые структуры или классы, которые поддерживают операции равенства (или комы для быстрого режима).
Функции
- любые типы данных, которые поддерживают сравнения или проверяют равенство
- возможность создания каскадных вложенных статусов коммутаторов.
- возможность прерывания или падения по операторам case
- возможность использования выражений case non constatnt
- можно включить быстрый статический/динамический режим с поиском дерева (для С++ 11)
Различия в Sintax с языковым переключателем
- заглавные слова
- нужны скобки для оператора CASE
- точка с запятой ';' в конце инструкций не допускается
- двоеточие ':' в операторе CASE не разрешено
- требуется одно из BREAK или FALL в конце оператора CASE.
Для языка C++97
используется линейный поиск.
Для C++11
и более современных можно использовать quick
режим поиска wuth tree, где оператор return в CASE становится недопустимым.
Реализация языка C
существует там, где используется char*
тип и сравнение строк с нулевым завершением.
Прочитайте о этой реализации коммутатора.
Ответ 10
Я думаю, причина в том, что в строках C не являются примитивными типами, как сказал tomjen, думайте в строке как массив char, поэтому вы не можете делать такие вещи, как:
switch (char[]) { // ...
switch (int[]) { // ...
Ответ 11
Чтобы добавить вариант с использованием простейшего возможного контейнера (нет необходимости в упорядоченной карте)... Я не стал бы беспокоиться о перечислении - просто поместите определение контейнера непосредственно перед переключателем, чтобы было легко увидеть, какое число представляет в каком случае.
Это выполняет хешированный поиск в 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
}
Ответ 12
В С++ вы можете использовать оператор switch только для int и char
Ответ 13
В С++ строки не являются гражданами первого класса. Строковые операции выполняются через стандартную библиотеку. Думаю, вот в чем причина. Кроме того, С++ использует оптимизацию таблицы ветвей для оптимизации операторов case switch. Посмотрите на ссылку.
http://en.wikipedia.org/wiki/Switch_statement
Ответ 14
Вы не можете использовать строку в case switch. Только разрешены int и char. Вместо этого вы можете попробовать enum для представления строки и использовать ее в блоке case switch, например
enum MyString(raj,taj,aaj);
Используйте его в инструкции swich case.
Ответ 15
Коммутаторы работают только с интегральными типами (int, char, bool и т.д.). Почему бы не использовать карту для сопряжения строки с номером, а затем использовать этот номер с помощью переключателя?
Ответ 16
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;
}
Ответ 17
во многих случаях вы можете запросить дополнительную работу, вытащив из строки строку char и включив ее. может закончиться тем, что нужно сделать вложенный ключ на charat (1), если ваши случаи начинаются с того же значения. любой, кто читает ваш код, по достоинству оценит намек, потому что большинство будет пытаться просто if-else-if
Ответ 18
Более функциональный обход проблемы с коммутатором:
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)
{
// ...
}
}
Ответ 19
Это потому, что С++ превращает ключи в таблицы перехода. Он выполняет тривиальную операцию над входными данными и переходит к соответствующему адресу без сравнения. Поскольку строка не является числом, а массивом чисел, С++ не может создать таблицу перехода из нее.
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)