• Qt 枚举变量,Q_ENUM,Q_FLAG,Q_NAMESPACE,Q_ENUM_NS,Q_FLAG_NS以及其他


    前言
      之前做一个比较大工程,核心数据里面有很多是枚举变量,需要频繁地使用枚举量到字符串和字符串到枚举量的操作,为了实现这些操作,我把每个枚举类型后面都附加了两个类似Enum_to_String()和String_to_Enum()的函数,程序显得很臃肿。这时候就非常羡慕C#或者java等兄弟语言,内核内置了枚举量和字符串转换的方法。
      最近读Qt文档时偶然间发现,Qt内核其实已经提供了这个转换机制,使得我们能用很少的代码完成枚举量和字符串的转换,甚至还能实现其他更酷更强大的功能,下面我们就来看看如何使用Qt的这个功能。
      简单来讲,Qt还是使用一组宏命令来完成枚举量扩展功能的(正如Qt的其他核心机制一样),这些宏包括Q_ENUM,Q_FLAG,Q_ENUM_NS,Q_FLAG_NS,Q_DECLARE_FLAGS,Q_DECLARE_OPERATORS_FOR_FLAGS,
    这些宏的实现原理和如何展开如何注册到Qt内核均不在本文的讲解范围,本文只讲应用。

    Q_ENUM的使用
    首先讲解最简单明了的宏Q_ENUM,先看代码:

     1 #include <QObject>
     2 
     3 class MyEnum : public QObject
     4 {
     5     Q_OBJECT
     6 public:
     7     explicit MyEnum(QObject *parent = nullptr);
     8     
     9     enum Priority
    10     {
    11         High = 1,
    12         Low = 2,
    13         VeryHigh = 3,
    14         VeryLow = 4
    15     };
    16     Q_ENUM(Priority)
    17 };

    这就是在类中定义了一个普通的枚举变量之后,额外加入了Q_ENUM(枚举类型名)这样的一个宏语句,那么加入了这个Qt引入的宏语句后,我们能得到什么收益呢?

    1 qDebug()<< MyEnum::High<< MyEnum::Low;                  //qDebug()可以直接打印出枚举类值的字符串名称
    2 QMetaEnum m = QMetaEnum::fromType<MyEnum::Priority>();  //since Qt5.5
    3 qDebug()<< "keyToValue:"<< m.keyToValue("VeryHigh");
    4 qDebug()<< "valueToKey:"<< m.valueToKey(MyEnum::VeryHigh);
    5 qDebug()<< "keyCount:"<< m.keyCount();

    输出是

    1 MyEnum::High MyEnum::Low
    2 keyToValue: 4
    3 valueToKey: VeryHigh
    4 keyCount: 4 

      可见,使用Q_ENUM注册过的枚举类型,可以不加修饰直接被qDebug()打印出来,另外通过静态函数QMetaEnum::fromType()可以获得一个QMetaEnum 对象,以此作为中介,能够轻松完成枚举量和字符串之间的相互转化。这一点恐怕是引入Q_ENUM机制最直接的好处。
      除此以外,QMetaEnum还提供了一个内部的索引,从1开始给每个枚举量按自然数顺序编号(注意和枚举量本身的数值是两回事),提供了int value(int index) 和const char *key(int index)
    两个便捷函数分别返回枚举量对应的数值和枚举量对应的字符串,配合keyCount() 函数可以实现枚举量的遍历:

    1 qDebug()<<m.name()<<":";
    2 for (int i = 0; i < m.keyCount(); ++i) {
    3     qDebug()<<m.key(i)<<m.value(i);
    4 }

    输出

    1 Priority :
    2 High 1
    3 Low 2
    4 VeryHigh 4
    5 VeryLow 8

    其中name()函数返回枚举类型名字。
    Q_ENUM使用起来很很简单,对不对?但是还是有几个注意事项需要说明:

    1.Q_ENUM只能在使用了Q_OBJECT或者Q_GADGET的类中,类可以不继承自QObject,但一定要有上面两个宏之一(Q_GADGET是Q_OBJECT的简化版,提供元对象的一部分功能,但不支持信号槽);
    2.Q_ENUM宏只能放置于所包含的结构体定义之后,放在前面编译器会报错,结构体定义和Q_ENUM宏之间可以插入其他语句,但不建议这样做;
    3.一个类头文件中可以定义多个Q_ENUM加持的结构体,结构体和Q_ENUM是一一对应的关系;
    4.Q_ENUM加持的结构体必须是公有的;
    5.Q_ENUM宏引入自Qt5.5版本,之前版本的Qt请使用Q_ENUMS宏,但Q_ENUMS宏不支持QMetaEnum::fromType()函数(这也是Q_ENUMS被弃用的原因)。


    Q_FLAG的引入解决什么问题?
    除了Q_ENUM,Qt中还有另一个类似的宏——Q_FLAG,着力弥补C++中结构体无法组合使用,和缺乏类型检查的缺点,怎么理解呢?我们看一个例子:
    在经典C++中,如果我们要定义一个表示方位的结构体:

    1 enum Orientation
    2 { 
    3 Up = 1, 
    4 Down = 2, 
    5 Left = 4, 
    6 Right = 8
    7 };

      注意这里枚举值被定义成等比数列,这个技巧给使用"|“操作符扩展留下了空间,比如,左上可以用Up | Left来简单表示,但是这里也带来了一个问题,Up | Left值是一个整型,并不在枚举结构Orientation中,如果函数使用Orientation作为自变量,编译器是无法通过的,为此往往把函数自变量类型改为整型,但因此也就丢掉了类型检查,输入量有可能是其他无意义的整型量,在运行时带来错误。
      Qt引入QFlags类,配合一组宏命令完美地解决了这个问题。
      QFlags是一个简单的类,可以装入一个枚举量,并重载了与或非等运算符,使得枚举量能进行与或非运算,且运算结果还是一个QFlags包装的枚举量。一个普通的枚举类型包装成QFlags型,需要使用Q_DECLARE_FLAGS宏,在全局任意地方使用”|"操作符计算自定义的枚举量,需要使用Q_DECLARE_OPERATORS_FOR_FLAGS宏。
    再看一段代码:

     1 class MyEnum : public QObject
     2 {
     3     Q_OBJECT
     4 public:
     5     explicit MyEnum(QObject *parent = nullptr);
     6 
     7     enum Orientation
     8     {
     9         Up = 1,
    10         Down = 2,
    11         Left = 4,
    12         Right = 8,
    13     };
    14     Q_ENUM(Orientation)        //如不使用Orientation,可省略
    15     Q_DECLARE_FLAGS(OrientationFlags, Orientation)
    16     Q_FLAG(OrientationFlags)
    17 };
    18 
    19 Q_DECLARE_OPERATORS_FOR_FLAGS(MyEnum::OrientationFlags)

      上面这段代码展示了使用Q_FLAG包装枚举定义的方法,代码中Q_DECLARE_FLAGS(Flags, Enum)实际上被展开成typedef QFlags< Enum > Flags,所以Q_DECLARE_FLAGS实际上是QFlags的定义式,之后才能使用Q_FLAG(Flags)把定义的Flags注册到元对象系统。Q_FLAG完成的功能和Q_ENUM是类似的,使得枚举量可以被QMetaEnum::fromType()调用。
    看一下使用代码:

     1 qDebug()<<(MyEnum::Up|MyEnum::Down);
     2 QMetaEnum m = QMetaEnum::fromType<MyEnum::OrientationFlags>();  //since Qt5.5
     3 qDebug()<< "keyToValue:"<<m.keyToValue("Up|Down");
     4 qDebug()<< "valueToKey:"<<m.valueToKey(Up|Down);
     5 qDebug()<< "keysToValue:"<<m.keysToValue("Up|Down");
     6 qDebug()<< "valueToKeys:"<<m.valueToKeys(Up|Down)<<endl;
     7 
     8 qDebug()<< "isFlag:"<<m.isFlag();
     9 qDebug()<< "name:"<<m.name();
    10 qDebug()<< "enumName:"<<m.enumName();               //since Qt5.12
    11 qDebug()<< "scope:"<<m.scope()<<endl;

    执行结果

     1 QFlags<MyEnum::Orientation>(Up|Down)
     2 keyToValue: -1
     3 valueToKey: 
     4 keysToValue: 3
     5 valueToKeys: "Up|Down" 
     6 
     7 isFlag: true
     8 name: OrientationFlags
     9 enumName: Orientation
    10 scope: MyEnum 

      可以看到,经过Q_FLAG包装之后,QMetaEnum具有了操作复合枚举量的能力,注意这时应当使用keysToValue()和valueToKeys()函数,取代之前的keyToValue()和valueToKey()函数。另外,isFlag()函数返回值变成了true,name()和enumName()分别返回Q_FLAG包装后和包装前的结构名。
      实际上此时类中是存在两个结构体的,如果在定义时加上了Q_ENUM(Orientation),则Orientation和OrientationFlags都能被QMetaEnum识别并使用,只不过通常我们只关注Q_FLAG包装后的结构体。
    这样我们顺便明白了Qt官方定义的许多枚举结构都是成对出现的原因,比如

    1 enum Qt::AlignmentFlag
    2 flags Qt::Alignment
    1 enum Qt::MatchFlag
    2 flags Qt::MatchFlags
    1 enum Qt::MouseButton
    2 flags Qt::MouseButtons

    再总结下Q_FLAG以及Q_DECLARE_FLAGS、Q_DECLARE_OPERATORS_FOR_FLAGS使用的要点吧:

    1.Q_DECLARE_FLAGS(Flags, Enum)宏将普通结构体Enum重新定义成了一个可以自由进行与或非操作的安全的结构体Flags。Q_DECLARE_FLAG出现在Enum定义之后,且定义之后Enum和Flags是同时存在的;
    2.Q_DECLARE_OPERATORS_FOR_FLAGS(Flags)赋予了Flags一个全局操作符“|”,没有这个宏语句,Flags量之间进行与操作后的结果将是一个int值,而不是Flags值。Q_DECLARE_OPERATORS_FOR_FLAGS应当定义在类外;
    3.Q_DECLARE_OPERATORS_FOR_FLAGS只提供了“或”操作,没有提供“与”“非”操作;
    4.Q_DECLARE_FLAGS和Q_DECLARE_OPERATORS_FOR_FLAGS都是和元对象系统无关的,可以脱离Q_FLAG单独使用,事实上这两个宏在Qt4就已经存在(不确定更早是否存在),而Q_FLAG是在Qt5.5版本才加入的;
    5.如果在我们的程序中不需要枚举变量的组合扩展,那么只用简单的Q_ENUM就好了。


    Q_NAMESPACE,Q_ENUM_NS和Q_FLAG_NS
    在Qt5.8之后,Qt引入了Q_NAMESPACE宏,这个宏能够让命名空间具备简化的元对象能力,但不支持信号槽(类似类里的Q_GADGET)。
    在使用了Q_NAMESPACE的命名空间中,可以使用Q_ENUM_NS和Q_FLAG_NS,实现类中Q_ENUM和Q_FLAG的功能。
    看一个例子:

     1 namespace MyNamespace
     2 {
     3 Q_NAMESPACE
     4 enum Priority
     5 {
     6     High = 1,
     7     Low = 2,
     8     VeryHigh = 4,
     9     VeryLow = 8,
    10 };
    11 Q_ENUM_NS(Priority)            //如不使用Priority,可省略
    12 Q_DECLARE_FLAGS(Prioritys, Priority)
    13 Q_FLAG_NS(Prioritys)
    14 Q_DECLARE_OPERATORS_FOR_FLAGS(Prioritys)
    15 }

      命名空间中Q_ENUM_NS和Q_FLAG_NS的使用和之前相类似,不再赘述。Q_DECLARE_OPERATORS_FOR_FLAGS则需要定义在命名空间之内。
    使用代码:

     1 using namespace MyNamespace;
     2 qDebug()<<(High|Low);
     3 QMetaEnum m = QMetaEnum::fromType<MyNamespace::Prioritys>();  //since Qt5.5
     4 qDebug()<< "keyToValue:"<<m.keyToValue("High|Low");
     5 qDebug()<< "valueToKey:"<<m.valueToKey(High|Low);
     6 
     7 qDebug()<< "keysToValue:"<<m.keysToValue("High|Low");
     8 qDebug()<< "valueToKeys:"<<m.valueToKeys(High|Low)<<endl;
     9 
    10 qDebug()<< "isFlag:"<<m.isFlag();
    11 qDebug()<< "name:"<<m.name();
    12 qDebug()<< "enumName:"<<m.enumName();               //since Qt5.12
    13 qDebug()<< "scope:"<<m.scope()<<endl;

    运行结果

     1 QFlags<MyNamespace::Priority>(High|Low)
     2 keyToValue: -1
     3 valueToKey: 
     4 keysToValue: 3
     5 valueToKeys: "High|Low" 
     6 
     7 isFlag: true
     8 name: Prioritys
     9 enumName: Priority
    10 scope: MyNamespace 

      可以看到,从定义到使用,和之前Q_FLAG几乎一模一样。
      在命名空间中使用Q_ENUM_NS或者Q_FLAG_NS,能让枚举结构体定义不再局限在类里,使我们有更多的选择。另外,在今后Qt的发展中,相信Q_NAMESPACE能带来更多的功能,我们拭目以待。

    新旧对比
      Qt一直是一个发展的框架,不断有新特性加入,使得Qt变得更易用。
      本文介绍的内容都是在Qt5.5版本以后才引入的,Q_NAMESPACE的内容甚至要到Qt5.8版本才引入,在之前Qt中也存在着枚举量的扩展封装——主要是Q_ENUMS和Q_FLAGS,这套系统虽然已经不建议使用,但是为了兼容性,还是予以保留。我们看看之前的系统如何使用的:
      枚举量定义基本一致,就是Q_ENUMS(Enum)宏放到定义之前,代码从略。
    使用上:

    1 QMetaObject object = MyEnum::staticMetaObject;                //before Qt5.5
    2 int index = object.indexOfEnumerator("Orientation");
    3 QMetaEnum m = object.enumerator(index);

    对比改进后的

    QMetaEnum m = QMetaEnum::fromType<MyEnum::Orientation>();  //since Qt5.5

      不仅仅是3行代码简化成1行,更重要的是Qt程序员终于不用再显式调用元对象QMetaObject了。改进的代码元对象机制同样在起着作用,但却变得更加隐蔽,更加沉默,使得程序员可以把精力更多放到功能的实现上,这大概就是Qt发展的方向。

    结语
      很多Qt程序员喜欢用旧版本编程,但是我是坚定的新版本拥趸,在给程序编写带来便利的同时,还能满足自己的好奇心,何乐而不为呢?

  • 相关阅读:
    [php]php设计模式 Interpreter(解释器模式)
    [php]php设计模式 Decorator(装饰模式)
    [php]php设计模式 Adapter(适配器模式)
    [php]php设计模式 Delegation(委托模式)
    [php]php设计模式 Builder(建造者模式)
    [python]django学习笔记 二
    [ruby]ruby on rails学习笔记1
    [php]php设计模式 Factory(工厂模式)
    pku3461 Oulipo (KMP)
    pku 2406 && pku 1961 Period && hdu3746 Cyclic Nacklace
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14970840.html
Copyright © 2020-2023  润新知