zl程序教程

您现在的位置是:首页 >  工具

当前栏目

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

Qt变量 以及 其他 枚举 Namespace Enum flag
2023-09-14 09:05:11 时间

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();

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

输出是

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开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓