C++中使用QString类型的switch/case语句

39

我想在我的程序中使用 switch-case,但编译器给出了以下错误:

switch expression of type 'QString' is illegal

我如何使用switch语句处理QString

我的代码如下:

bool isStopWord( QString word )
{
bool flag = false ;

switch( word )
{
case "the":
    flag = true ;
    break ;
case "at" :
    flag = true ;
    break ;
case "in" :
    flag = true ;
    break ;
case "your":
    flag = true ;
    break ;
case "near":
    flag = true ;
    break ;
case "all":
    flag = true ;
    break ;
case "this":
    flag = true ;
    break ;
}

return flag ;
}

3
请贴出出现问题的代码以及编译器所给出的“精确”错误信息。我们无法猜测您做错了什么。 - Mat
一个在Qt中的switch语句就像任何C++中的switch语句一样。您需要发布展示您遇到问题的代码。 - Håvard S
http://stackoverflow.com/help/accepted-answer - phuclv
@HåvardS 除了在 Qt 中有一种特殊的方法可以打开字符串。 - phuclv
对于任何阅读此代码的人来说,很明显可以将其转换为使用“||”的一行代码,或者更不明显但可能更清晰的是在集合中检查包含。 - user4945014
14个回答

36

你可以在迭代之前创建一个QStringList,像这样:

QStringList myOptions;
myOptions << "goLogin" << "goAway" << "goRegister";

/*
goLogin = 0
goAway = 1
goRegister = 2
*/

那么:

switch(myOptions.indexOf("goRegister")){
  case 0:
    // go to login...
    break;

  case 1:
    // go away...
    break;

  case 2:
    //Go to Register...
    break;

  default:
    ...
    break;
}

8
它唯一的问题是它脆弱,即重叠字符串的值。真正的解决方案是正确的映射,尽管这不是 Qt 的想法,而是一个普遍的概念。 - László Papp
我非常喜欢这个想法,为什么我没想到呢?谢谢。 - Beyondo

33

如何将QString与switch语句一起使用?

不行。在C++语言中,switch语句只能用于整数或枚举类型。虽然可以正式地将类类型的对象放入switch语句中,但这只意味着编译器将寻找用户定义的转换来将其转换为整数或枚举类型。


这听起来很酷 - 但对我来说不够详细。你能提供示例代码吗? - TSG
1
@Michelle:将通用字符串转换为整数或枚举类型没有实际意义,这意味着在switch语句中使用一般的QString是没有意义的。因此,我的答案是:“无法完成”。在一般情况下,您可以忘记switch语句,而改用一系列if语句。 - AnT stands with Russia
@Michelle:一个具体的情况是,当您的字符串来自受限制和预定义的字符串表时,使用switch语句变得可能。在这种情况下,您可以将字符串转换为其索引,并将该索引用作选择器值在switch语句中。这正是olarva答案所说明的。 - AnT stands with Russia
实际上,这可能是通过标准库实现的,而不是核心语言,但对于许多人来说,C++也意味着标准库。 - László Papp
@AnT 有一种有意义的方法可以使用 QMetaEnum 将通用字符串转换为枚举值,这意味着可以在 QString 上进行切换。 - phuclv

5
在C++中,不能直接使用字符串进行开关操作。但是,在Qt中可以使用QMetaEnum实现,如此示例所示:Q_ENUM and how to switch on a string。甚至不需要像Anthony Hilyard的答案中那样使用C++14,并且匹配的情况不是字符串的哈希值,因此零碰撞的机会。

基本上,QMetaEnum可以将字符串转换为枚举值,反之亦然,因此我们将使用它来跳转到正确的分支。一个小限制是字符串是枚举值,因此字符串必须是有效的C ++标识符。但这很容易解决,只需根据需要使用特定规则替换特殊字符即可。

为此,首先在类声明中声明枚举,并将要用作开关情况的字符串作为枚举器名称。然后使用Q_ENUMS将枚举添加到元数据中,以便程序稍后搜索。

#include <QMetaEnum>

class TestCase : public QObject
{
    Q_OBJECT
    Q_ENUMS(Cases)        // metadata declaration

    QMetaObject MetaObject;
    QMetaEnum MetaEnum;   // enum metadata

    TestCase() :
    // get information about the enum named "Cases"
    MetaObject(this->staticMetaObject),
    MetaEnum(MetaObject.enumerator(MetaObject.indexOfEnumerator("Cases"))
    {}

public:
    explicit Test(QObject *parent = 0);

    enum Cases
    {
        THE, AT, IN, THIS // ... ==> strings to search, case sensitive
    };

public slots:
    void SwitchString(const QString &word);
};

然后,在使用QMetaEnum::keyToValue将字符串转换为相应的值之后,只需在SwitchString内部实现所需的开关即可。

比较区分大小写,因此如果您想要进行不区分大小写的搜索,请先将输入字符串转换为大/小写。您还可以进行其他必要的字符串转换。例如,如果您需要在C++标识符中切换带有空格或禁止字符的字符串,则可以将这些字符转换/删除/替换,使该字符串成为有效的标识符。

void TestCase::SwitchString(const QString &word)
{
    switch (MetaEnum.keyToValue(word.toUpper().toLatin1()))
    // or simply switch (MetaEnum.keyToValue(word)) if no string modification is needed
    {
        case THE:  /* do something */ break;
        case AT:   /* do something */ break;
        case IN:   /* do something */ break;
        case THIS: /* do something */ break;
        default:   /* do something */ break;
    }
}

然后只需要使用类来切换字符串。例如:

TestCase test;
test.SwitchString("At");
test.SwitchString("the");
test.SwitchString("aBCdxx");

1
感谢详细的说明。 - SaddamBinSyed

5
@DomTomCat的回答已经涉及到了这个问题,但由于问题特别询问Qt,所以有更好的方法。
Qt已经为QString提供了哈希函数,但不幸的是,Qt4的qHash没有被限定为constexpr。幸运的是,Qt是开源的,因此我们可以将qHash功能复制到自己的constexpr哈希函数中并使用它! Qt4's qHash source 我已经修改了它,只需要一个参数(字符串文字始终是以空字符结尾的)。
uint constexpr qConstHash(const char *string)
{
    uint h = 0;

    while (*string != 0)
    {
        h = (h << 4) + *string++;
        h ^= (h & 0xf0000000) >> 23;
        h &= 0x0fffffff;
    }
    return h;
}

一旦你定义了它,你可以在 switch 语句中使用它,如下所示:
QString string;
// Populate the QString somehow.

switch (qHash(string))
{
    case qConstHash("a"):
        // Do something.
        break;
    case qConstHash("b"):
        // Do something else.
        break;
}

由于此方法使用Qt用于计算哈希的相同代码,因此它具有与QHash相同的哈希冲突抵抗力,通常非常好。缺点是需要一个相当新的编译器--由于constexpr哈希函数中有非返回语句,因此需要C++14。

4
如果您使用现代的C++编译器,那么您可以计算字符串的编译时哈希值。在这个答案中,有一个相当简单的constexpr哈希函数示例。
因此,解决方案可能如下所示:
// function from https://dev59.com/5XI95IYBdhLWcg3w3yFU#2112111
// (or use some other constexpr hash functions from this thread)
unsigned constexpr const_hash(char const *input) {
    return *input ?
    static_cast<unsigned int>(*input) + 33 * const_hash(input + 1) :
    5381;
}

QString switchStr = "...";
switch(const_hash(switchStr.toStdString().c_str()))
{
case const_hash("Test"):
    qDebug() << "Test triggered";
    break;
case const_hash("asdf"):
    qDebug() << "asdf triggered";
    break;
default:
    qDebug() << "nothing found";
    break;
}

这仍然不是一个完美的解决方案。可能会出现哈希冲突(因此每次添加/更改case时,请测试您的程序),如果您想使用奇特或utf字符,则在从QString转换为char*时必须小心。

对于c++ 11,将CONFIG += c++11添加到您的项目中,对于Qt5: QMAKE_CXXFLAGS += -std=c++11,对于Qt4同样如此。


1

如先前所述,这不是Qt的问题,switch语句只能使用常量表达式,看看集合类,QSet是一个很好的解决方案。

void initStopQwords(QSet<QString>& stopSet)
{
    // Ideally you want to read these from a file
    stopSet << "the";
    stopSet << "at";
    ...

}

bool isStopWord(const QSet<QString>& stopSet, const QString& word)
{
    return stopSet.contains(word);
}

1
然而,如果您将其视为Qt问题,则可以将字符串转换为枚举值以在switch中使用,因为Qt已经支持此功能。 - phuclv
1
@LưuVĩnhPhúc:这种情况通常发生在 C++ 程序员没有 Qt 专业知识的情况下回答问题。 - László Papp

1

试一下这个:

// file qsswitch.h
#ifndef QSSWITCH_H
#define QSSWITCH_H

#define QSSWITCH(__switch_value__, __switch_cases__) do{\
    const QString& ___switch_value___(__switch_value__);\
    {__switch_cases__}\
    }while(0);\

#define QSCASE(__str__, __whattodo__)\
    if(___switch_value___ == __str__)\
    {\
    __whattodo__\
    break;\
    }\

#define QSDEFAULT(__whattodo__)\
    {__whattodo__}\

#endif // QSSWITCH_H

如何使用:

#include "qsswitch.h"

QString sW1 = "widget1";
QString sW2 = "widget2";

class WidgetDerived1 : public QWidget
{...};

class WidgetDerived2 : public QWidget
{...};

QWidget* defaultWidget(QWidget* parent)
{
    return new QWidget(...);
}

QWidget* NewWidget(const QString &widgetName, QWidget *parent) const
{
    QSSWITCH(widgetName,
             QSCASE(sW1,
             {
                 return new WidgetDerived1(parent);
             })
             QSCASE(sW2,
             {
                 return new WidgetDerived2(parent);
             })
             QSDEFAULT(
             {
                 return defaultWidget(parent);
             })
             )
}

这里有一些简单的宏魔法。在预处理之后,代码如下:

QSSWITCH(widgetName,
         QSCASE(sW1,
         {
             return new WidgetDerived1(parent);
         })
         QSCASE(sW2,
         {
             return new WidgetDerived2(parent);
         })
         QSDEFAULT(
         {
             return defaultWidget(parent);
         })
         )

将会像这样工作:
// QSSWITCH
do{
        const QString& ___switch_value___(widgetName);
        // QSCASE 1
        if(___switch_value___ == sW1)
        {
            return new WidgetDerived1(parent);
            break;
        }

        // QSCASE 2
        if(___switch_value___ == sW2)
        {
            return new WidgetDerived2(parent);
            break;
        }

        // QSDEFAULT
        return defaultWidget(parent);
}while(0);

添加一些代码的解释会很有帮助。 - fedorqui
2
在我看来,与一系列if-else相比,它仍然不太清晰。 - phuclv
非常丑陋。而且第二、第三等等的条件判断语句都没有。执行速度甚至非常慢。if和switch case相比,后者的执行速度要慢得多。因为它必须逐个比较每个if语句是否为真。所以请不要使用它! - Meister Schnitzel
坦率地说,虽然意图是好的,但即使我的生命取决于它,我也不会使用这样的自定义结构。正如@MeisterSchnitzel所指出的那样,代码很慢(使用if-else-if会更好,但宏已经很笨重了,这样做会让宏变得更加难读)。 - rbaleksandar

0
case "the":
    //^^^ case label must lead to a constant expression

我不了解qt,但你可以试试这个。如果QString与普通的std::string没有区别,你可以避免使用switch,直接使用==进行比较。

if( word == "the" )
{
   // ..
}
else if( word == "at" )
{
   // ..
}
// ....

1
“the” 绝对是一个常量表达式。它的值为 t 且其地址指向一个 char 类型的变量。但是一个地址并不是一个整数。 - wilhelmtell

0
这个在我看来似乎更加理智些。
bool isStopWord( QString w ) {
    return (
        w == "the" ||
        w == "at" ||
        w == "in" ||
        w == "your" ||
        w == "near" ||
        w == "all" ||
        w == "this"
    );
}

哦,是的,Rust 和 Haskell 风格。我看到很多人都在使用这种风格。 - László Papp

0
来晚了,这里是我一段时间前想出的解决方案,完全遵守所请求的语法,并且适用于c++11及以上版本。
#include <uberswitch/uberswitch.hpp>

bool isStopWord( QString word )
{
bool flag = false ;

uswitch( word )
{
ucase ("the"):
    flag = true ;
    break ;
ucase ("at") :
    flag = true ;
    break ;
ucase ("in") :
    flag = true ;
    break ;
ucase ("your"):
    flag = true ;
    break ;
ucase ("near"):
    flag = true ;
    break ;
ucase ("all"):
    flag = true ;
    break ;
ucase ("this"):
    flag = true ;
    break ;
}

return flag ;
}

唯一需要注意的区别是使用switch代替switch,并使用ucase代替case,在ucase值周围加上括号(必需,因为那是一个宏)。
以下是代码:https://github.com/falemagn/uberswitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接