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



компиляция следующего кода и получил ошибку type illegal.



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


вы не можете использовать строку в либо switch или case. Зачем? Есть ли какое-либо решение, которое хорошо работает для поддержки логики, подобной включению строк?

1449   18  

18 ответов:

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

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

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

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

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

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

код, написанный Стефан рук 25 июля 2001 года.

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

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

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

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

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

использование статики в коде codeguru возможно при поддержке компилятора для списков инициализаторов, что означает VS 2013 plus. ССЗ 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]);
}

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

unordered_map для потенциальной амортизации O(1):что является лучшим способом, чтобы использовать HashMap в языке 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;
    }
}

выход:

one 1
two 2
three 3
foobar -1

использование внутри методов с static

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

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

необходимо было преобразовать захват лямбда-контекста [&] в аргумент, или это было бы неопределенно:const статическая автоматическая лямбда используемая с захватом мимо ссылка

пример, который производит тот же результат, что и выше:

#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++ и C переключатели работают только на целочисленных типах. Вместо этого используйте лестницу if else. C++, очевидно, мог бы реализовать какой - то оператор swich для строк-я думаю, никто не думал, что это стоит, и я согласен с ними.

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

особенности

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

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

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

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

читать подробнее о реализация данного переключателя.

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

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

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

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
}

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

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

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

это делает хэшированный поиск в 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

}

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

enum MyString(raj,taj,aaj);

используйте его int оператор 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), если ваши случаи начинаются с того же значения. любой, кто читает ваш код, будет признателен за подсказку, хотя, потому что большинство будет пробовать только если-иначе-если

это потому, что 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
...

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

Comments

    Ничего не найдено.