Switch deyimi neden dizelere uygulanamıyor?


227

Aşağıdaki kodu derleme ve hatası var type illegal.

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

Ya dizeyi kullanamaz switchveya case. Neden? Dizeleri açmaya benzer mantığı desteklemek için iyi çalışan bir çözüm var mı?


6
Harita yapısını gizleyen ve bir MACRO'nun arkasındaki numaralandırma alternatifi var mı?
balki

@balki Güçlendirme konusunda emin değilim ama böyle makrolar yazmak kolay. Qt durumunda eşlemeyi gizleyebilirsinizQMetaEnum
phuclv

Yanıtlar:


189

Tür sistemi ile ilgili olmasının nedeni. C / C ++, dizeleri gerçekten bir tür olarak desteklemez. Sabit bir char dizisi fikrini destekliyor ancak bir dize kavramını tam olarak anlamıyor.

Bir switch deyimi için kod oluşturmak amacıyla, derleyicinin iki değerin eşit olmasının ne anlama geldiğini anlaması gerekir. Ints ve enum gibi öğeler için bu önemsiz bir bit karşılaştırmasıdır. Ancak derleyici 2 dize değerini nasıl karşılaştırmalı? Büyük / küçük harfe duyarlı, duyarsız, kültüre duyarlı, vb ... Bir ipin tam olarak farkında olmadan bu doğru bir şekilde cevaplanamaz.

Ayrıca, C / C ++ anahtar deyimleri genellikle dal tabloları olarak oluşturulur . Dize stili bir anahtar için bir şube tablosu oluşturmak neredeyse o kadar kolay değildir.


11
Dal tablosu argümanı geçerli olmamalıdır - bu derleyici yazarının kullanabileceği olası bir yaklaşımdır. Bir üretim derleyicisi için, anahtarın karmaşıklığına bağlı olarak sıklıkla birkaç yaklaşım kullanılmalıdır.
Süpürgelikler

5
@plinth, çoğunlukla tarihsel nedenlerden dolayı oraya koydum. "C / C ++ bunu neden yapıyor?" Sorularının çoğu derleyicinin geçmişi tarafından kolayca yanıtlanabilir. Yazdıkları sırada, C meclisin yüceltildi ve bu yüzden anahtar gerçekten uygun bir şube tablosuydu.
JaredPar

114
Nasıl derleyici nasıl if ifadeleri 2 dize değerleri karşılaştırmak bilir biliyor ama aşağı değil aynı şeyi yapmak için anahtar deyimleri anlayabiliyorum çünkü aşağı oy.

15
İlk 2 paragrafın geçerli nedenler olduğunu düşünmüyorum. Özellikle std::stringdeğişmezler eklendiğinde C ++ 14 beri . Çoğunlukla tarihseldir. Ancak akla gelen bir sorun switchşu anda çalışma şekliyle , derleme zamanında yinelenen cases saptanması gerektiğidir ; ancak bu, dizeler için o kadar kolay olmayabilir (çalışma zamanı yerel ayarı vb. dikkate alınarak). Sanırım böyle bir şey constexprvaka gerektirmeli ya da belirtilmemiş davranışlar eklemeli (asla yapmak istediğimiz bir şey).
MM

8
İki std::stringdeğerin ve hatta bir std::stringconst char dizisiyle (yani işleç == kullanarak) nasıl karşılaştırılacağına dair net bir tanım vardır. Bu, etiketlerin ömrü gibi şeyler hakkında bazı sorular açacaktı, ancak tüm bunlar, teknik bir zorluk değil, öncelikle bir dil tasarımı kararıdır.
MikeMB

60

Daha önce de belirtildiği gibi, derleyiciler switchifadeleri mümkün olduğunca O (1) zamanlamasına göre optimize eden arama tabloları oluşturmayı sever . Bunu, C ++ Dilinin bir dize türüne sahip olmadığı gerçeğiyle birleştirin - std::stringStandart Kitaplığın bir parçası olan ve kendi başına Dilin bir parçası değildir.

Düşünmek isteyebileceğiniz bir alternatif sunacağım, geçmişte iyi etki için kullandım. Dizenin kendisini değiştirmek yerine, dizeyi girdi olarak kullanan bir karma işlevinin sonucunu değiştirin. Önceden belirlenmiş bir dizi dizisi kullanıyorsanız, kodunuz dizeyi değiştirmek kadar açık olacaktır:

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 derleyicisinin bir switch deyimiyle ne yapacağını takip eden bir sürü bariz optimizasyon var ... bunun nasıl komik olduğu.


15
Gerçekten hash değil çünkü bu gerçekten hayal kırıklığı yaratıyor. Modern C ++ ile bir constexpr karma fonksiyonu kullanarak derleme zamanında hash yapabilirsiniz. Çözümünüz temiz görünüyor ama merdiven ne yazık ki devam ederse tüm bu kötü. Aşağıdaki harita çözümleri daha iyi olacak ve fonksiyon çağrısından da kaçınacaktır. Buna ek olarak, iki harita kullanarak hata günlüğü oluşturmak için yerleşik metin de kullanabilirsiniz.
Dirk Bester

Ayrıca lambdas ile numaralandırmayı da önleyebilirsiniz: stackoverflow.com/a/42462552/895245
Ciro Santilli 郝海东 冠状 病 六四 事件

Karma bir bağlam işlevi olabilir mi? Std :: string yerine const char * ilettiğiniz göz önüne alındığında.
Victor Stone

Ama neden? Bir anahtarın üstünde if deyimini her zaman kullanabilirsiniz. Her ikisinin de minimum etkisi vardır, ancak bir anahtarla performans avantajları if-else aramasıyla silinir. Sadece bir if-else kullanmak marjinal olarak daha hızlı, ancak daha da önemlisi, önemli ölçüde daha kısa olmalıdır.
Zoe

20

C ++

constexpr karma işlevi:

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
}

1
Vakalarınızın hiçbirinin aynı değere sahip olmadığından emin olmalısınız. Ve o zaman bile, örneğin hash ("one") ile aynı değere sahip olan diğer dizelerin anahtarınızdaki ilk "bir şeyi" yanlış yapacağı bazı hatalarınız olabilir.
David Ljung Madison Stellar

Biliyorum, ama aynı değere hashes derlemek alışkanlık ve zamanında fark edeceksiniz.
Nick

İyi bir nokta - ancak bu, anahtarınızın bir parçası olmayan diğer dizeler için karma çarpışmayı çözmez. Önemli olmayan bazı durumlarda, ancak bu genel bir "git" çözümü ise, bir noktada bir güvenlik sorunu veya benzeri bir şey olduğunu hayal edebiliyordum.
David Ljung Madison Stellar

7
operator ""Kodu daha güzel hale getirmek için bir ekleyebilirsiniz . constexpr inline unsigned int operator "" _(char const * p, size_t) { return hash(p); }Ve case "Peter"_: break; Demo
hare1039

15

Görünüşe göre yukarıdaki @MarmouCorp değil, http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm C ++ 11 güncellemesi

Dizeler ve sınıf numaralandırması arasında dönüştürmek için iki harita kullanır (değerleri içinde yer aldığından düz numaralandırmadan daha iyi ve hoş hata iletileri için geriye doğru arama).

Codeguru kodunda statik kullanımı, VS 2013 plus anlamına gelen başlatıcı listeleri için derleyici desteği ile mümkündür. Gcc 4.8.1 onunla Tamam, ne kadar geri uyumlu olacağını emin değilim.

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

Daha sonra dize değişmezleri gerektiren bir çözüm buldum ve derleme zamanında vaka dizeleri hash ve anahtar dizesini çalışma zamanında hash yapabilirsiniz zaman (C ++ 14 veya 17 sanırım) derlemek gerekir. Gerçekten uzun anahtarlar için belki de önemli, ancak bu daha da az geriye dönük uyumludur.
Dirk Bester

Derleme zamanı çözümünü burada paylaşır mısınız lütfen? Teşekkürler!
qed

12

Sorun, optimizasyon nedenleriyle C ++ 'daki switch deyiminin ilkel türlerden başka bir şey üzerinde çalışmadığı ve bunları yalnızca derleme zaman sabitleriyle karşılaştırabilmenizdir.

Muhtemelen kısıtlamanın nedeni, derleyicinin, kodu bir cmp komutuna ve adresin hesaplandığı çalışma zamanına göre adresin hesaplandığı bir gotoma derleyen bir tür optimizasyon uygulayabilmesidir. Dallanma ve ve döngüler modern CPU'larla iyi oynamadığından, bu önemli bir optimizasyon olabilir.

Bunun etrafından dolaşmak için korkarım eğer if ifadelerine başvurmanız gerekecek.


Dizelerle çalışabilen bir switch deyiminin optimize edilmiş bir versiyonu kesinlikle mümkündür. İlkel türler için kullandıkları kod yolunu tekrar kullanamayacakları gerçeği, yapamayacakları std::stringve diğerlerinin dilde ilk vatandaş olmaları ve etkili bir algoritma ile geçiş deyiminde desteklemeleri anlamına gelmez .
ceztko

10

std::map + C ++ 11 numaralandırma olmadan lambdas deseni

unordered_mapitfa edilmiş potansiyel için O(1): C ++ bir HashMap kullanmanın en iyi yolu nedir?

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

Çıktı:

one 1
two 2
three 3
foobar -1

İçinde yöntem kullanımı static

Bu kalıbı sınıflar içinde verimli bir şekilde kullanmak için lambda haritasını statik olarak başlatın ya da O(n)sıfırdan oluşturmak için her seferinde ödeme yapın .

Burada {}bir staticyöntem değişkeninin başlatılmasıyla kurtulabiliriz : Sınıf yöntemlerindeki statik değişkenler , ama aynı zamanda şu yöntemleri de kullanabiliriz: C ++? Özel statik nesneleri başlatmam gerekiyor

Lambda bağlamı yakalamasını [&]bir argümana dönüştürmek gerekiyordu veya bu tanımsız olurdu: referansla yakalama ile kullanılan const statik otomatik lambda

Yukarıdaki ile aynı çıktıyı üreten örnek:

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

3
Bu yaklaşım ile switchifade arasında bir fark olduğunu unutmayın . Bir deyimdeki vaka değerlerinin çoğaltılması switchderleme zamanı hatasıdır. std::unordered_mapSessizce kullanmak yinelenen değerleri kabul eder.
D.Shawley

6

C ++ ve C anahtarlarında yalnızca tamsayı türlerinde çalışır. Bunun yerine bir if merdiveni kullanın. C ++ açıkça dizeleri için bir çeşit swich ifadesi uygulayabilirdi - sanırım kimse bunu değerli bulmadı ve ben onlara katılıyorum.


kabul etti, ancak bunu kullanmanın neyin mümkün olmadığını biliyor musun
yesraaj

Tarih? Gerçek sayıları, işaretçileri ve yapıları (C'nin sadece diğer veri türlerini) açmak sanse yapmaz, bu nedenle C tamsayılarla sınırlar.

Özellikle örtük dönüşümlere izin veren sınıfları açarsanız bir kez gerçekten iyi vakit geçirirsiniz.
Sharptooth

6

Neden olmasın? Eşit sözdizimi ve aynı semantik ile switch uygulamasını kullanabilirsiniz . CDil, tüm nesneleri ve dizeleri nesne yoktur, ancak dizeleri içinde Colan null işaretçi tarafından başvurulan dizeleri sonlandırıldı. C++Dil nesneleri karşılaştırma veya nesneler Eşitlikler kontrol etmek için aşırı yük fonksiyonları yapmak imkanı vardır. As Colarak C++için dizeleri için böyle anahtarı için esnek yeterlidir C dil ve her tür nesneler için desteğin comparaison veya onay eşitlik C++dili. Ve modern C++11, bu anahtar uygulamasının yeterince etkili olmasına izin verir.

Kodunuz şöyle olacaktır:

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"

Daha karmaşık türler std::pairsveya eşitlik işlemlerini (veya hızlı mod için koalisyonları ) destekleyen herhangi bir yapı veya sınıfı kullanmak mümkündür .

Özellikleri

  • karşılaştırmaları veya eşitliği kontrol eden her türlü veri
  • basamaklı iç içe anahtar statüleri oluşturma imkanı.
  • vaka ifadelerini kırma veya gözden geçirme olasılığı
  • Sabit olmayan vaka ifadelerini kullanma imkanı
  • ağaç arama ile hızlı statik / dinamik modu etkinleştirmek mümkün (C ++ 11 için)

Dil anahtarı ile sintaks farkları

  • büyük harfli anahtar kelimeler
  • CASE deyimi için parantez gerekiyor
  • noktalı virgül ';' ifadelerin sonunda izin verilmiyor
  • CASE deyiminde iki nokta üst üste ':' kullanılamaz
  • CASE ifadesinin sonunda BREAK veya FALL anahtar kelimelerinden birine ihtiyacınız var

Kullanılan C++97dil için doğrusal arama. CASE'te return ifadesine izin verilmeyen mod wuth tree arama C++11özelliğini kullanmak için ve daha modern . Nerede dil uygulaması var tipi ve sıfır sonlandırılmış dize karşılaştırmalar kullanılır.quickCchar*

Oku ilgili daha fazla bu anahtar uygulaması.


6

Mümkün olan en basit kapsayıcıyı kullanarak bir varyasyon eklemek için (sıralı bir haritaya gerek yok) ... Bir numaralandırma ile uğraşmazdım - sadece kap tanımını anahtarın hemen önüne koyun, böylece hangi sayıyı temsil ettiğini görmek kolay olacaktır hangi durumda.

Bu, içinde karma bir arama yapar ve switch deyimini çalıştırmak için unordered_mapilişkili olanı kullanır int. Oldukça hızlı olmalı. Bu kapsayıcıyı yaptığım gibi, atbunun yerine kullanıldığını unutmayın . Kullanmak tehlikeli olabilir - dize haritada değilse yeni bir eşleme oluşturacaksınız ve sonuç olarak tanımsız sonuçlar veya sürekli büyüyen bir harita ortaya çıkabilir.[]const[]

at()Dize haritada değilse işlevin bir istisna atacağını unutmayın . Bu yüzden önce kullanarak test etmek isteyebilirsiniz 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;


}

Tanımlanmamış bir dize testi olan sürüm şu şekildedir:

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

}

4

Nedeni C dizelerinde ilkel tipler olmadığını düşünüyorum, tomjen'in dediği gibi, bir karakter dizisi olarak bir dizede düşünün, böylece aşağıdaki gibi şeyleri yapamazsınız:

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

3
Bakmadan, bir karakter dizisi muhtemelen doğrudan bir integral türüne dönüşen bir char * 'a dejenere olur. Yani, iyi derlenebilir, ama kesinlikle ne istersen yapmaz.
David Thornley

3

C ++ dizeleri birinci sınıf vatandaş değildir. Dize işlemleri standart kitaplık aracılığıyla yapılır. Bence, nedeni bu. Ayrıca, C ++ anahtar durum bildirimlerini en iyi duruma getirmek için şube tablosu optimizasyonunu kullanır. Bağlantıya bir göz atın.

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


2

C ++ 'da yalnızca int ve char üzerinde bir switch deyimi kullanabilirsiniz


3
Bir karakter de bir int'e dönüşür.
Mart'ta Strager

İşaretçiler de yapabilir. Bu, bazen farklı bir dilde anlamlı olacak bir şey derleyebileceğiniz anlamına gelir, ancak doğru çalışmaz.
David Thornley

Aslında kullanabilir longve long longhangi olmayacaktır dönüşebilir int. Orada kesilme riski yoktur.
MSalters


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

    }

4
Bu kod soruyu cevaplayabilirken, bu kodun soruyu neden ve / veya nasıl cevapladığı konusunda ek bağlam sağlamak uzun vadeli değerini arttırır.
Benjamin

0

birçok durumda, dizeden ilk karakteri çekip açarak fazladan iş yapmak isteyebilirsiniz. durumlarınız aynı değerle başlarsa, charat (1) üzerinde yuvalanmış bir anahtar yapmak zorunda kalabilirsiniz. kodunuzu okuyan herkes bir ipucu takdir edecektir çünkü çoğu sadece eğer if-else-if


0

Anahtar sorununa daha işlevsel bir çözüm:

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

-1

Anahtar durumunda dize kullanamazsınız.Sadece int & char'a izin verilir. Bunun yerine dizeyi temsil etmek için enum'u deneyebilir ve anahtar durum bloğunda kullanabilirsiniz.

enum MyString(raj,taj,aaj);

Swich case deyiminde kullanın.



-1

Anahtarlar yalnızca integral tiplerle (int, char, bool vb.) Çalışır. Bir dizeyi sayı ile eşleştirmek ve daha sonra bu sayıyı anahtarla kullanmak için neden bir harita kullanmıyorsunuz?


-2

Çünkü C ++ anahtarları atlama tablolarına dönüştürür. Giriş verileri üzerinde önemsiz bir işlem gerçekleştirir ve karşılaştırma yapmadan doğru adrese atlar. Bir dize bir sayı değil, bir sayı dizisi olduğundan, C ++ ondan bir atlama tablosu oluşturamaz.

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


4
C ++ sözdiziminin belirli bir uygulamasını gerektirmez. Bir saf cmp/ jccuygulama, C ++ Standardına göre geçerli olabilir.
Ruslan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.