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

  • 前言
  • 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,先看代码:

#include <QObject>class MyEnum : public QObject
{Q_OBJECT
public:explicit MyEnum(QObject *parent = nullptr);enum Priority{High = 1,Low = 2,VeryHigh = 3,VeryLow = 4};Q_ENUM(Priority)
};

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

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

输出结果如下:

注意:如果没有用Q_ENUM(Priority),则会报如下错误:

且此时qDebug() << MyEnum::High << MyEnum::Low;      输出的是枚举类型表示的数值即1和2而不是字符串。  m.scope()返回枚举所在类名,本类中的枚举在MyEnum类的内部定义,所以返回的是MyEnum,m.name()返回是枚举名,本例的枚举名是Priority,如果将枚举定义放到内的外部,则m.scope()返回空。另外对于Qt的Qt's Property System、Meta-Object System默认是不支持枚举类型的,即如下代码:

Q_PROPERTY(EnFlowOrient floworient READ flowOrient WRITE setFlowOrient STORED true DESIGNABLE true)

中的EnFlowOrient枚举定义如下:

// 油液流动的方向enum class EnFlowOrient{eRightOrDownFlow,  eLeftOrUpFlow,     };

默认是不支持的,要使其支持枚举,也必须用用Q_ENUM(EnFlowOrient);声明且枚举必须放在类内部声明且必须是public才行。

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

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

输出:

Priority :
High 1
Low 2
VeryHigh 4
VeryLow 8

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

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

Q_FLAG的引入解决什么问题?

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

enum Orientation
{
Up = 1,
Down = 2,
Left = 4,
Right = 8
};

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

class MyEnum : public QObject
{Q_OBJECT
public:explicit MyEnum(QObject *parent = nullptr);enum Orientation{Up = 1,Down = 2,Left = 4,Right = 8,};Q_ENUM(Orientation)        //如不使用Orientation,可省略Q_DECLARE_FLAGS(OrientationFlags, Orientation)Q_FLAG(OrientationFlags)
};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()调用。
看一下使用代码:

qDebug()<<(MyEnum::Up|MyEnum::Down);
QMetaEnum m = QMetaEnum::fromType<MyEnum::OrientationFlags>();  //since Qt5.5
qDebug()<< "keyToValue:"<<m.keyToValue("Up|Down");
qDebug()<< "valueToKey:"<<m.valueToKey(Up|Down);
qDebug()<< "keysToValue:"<<m.keysToValue("Up|Down");
qDebug()<< "valueToKeys:"<<m.valueToKeys(Up|Down)<<endl;qDebug()<< "isFlag:"<<m.isFlag();
qDebug()<< "name:"<<m.name();
qDebug()<< "enumName:"<<m.enumName();               //since Qt5.12
qDebug()<< "scope:"<<m.scope()<<endl;

执行结果:

QFlags<MyEnum::Orientation>(Up|Down)
keyToValue: -1
valueToKey:
keysToValue: 3
valueToKeys: "Up|Down" isFlag: true
name: OrientationFlags
enumName: Orientation
scope: MyEnum

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

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

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

Q_DECLARE_FLAGS(Flags, Enum)宏将普通结构体Enum重新定义成了一个可以自由进行与或非操作的安全的结构体Flags。Q_DECLARE_FLAG出现在Enum定义之后,且定义之后Enum和Flags是同时存在的;
Q_DECLARE_OPERATORS_FOR_FLAGS(Flags)赋予了Flags一个全局操作符“|”,没有这个宏语句,Flags量之间进行与操作后的结果将是一个int值,而不是Flags值。Q_DECLARE_OPERATORS_FOR_FLAGS应当定义在类外;
Q_DECLARE_OPERATORS_FOR_FLAGS只提供了“或”操作,没有提供“与”“非”操作;
Q_DECLARE_FLAGS和Q_DECLARE_OPERATORS_FOR_FLAGS都是和元对象系统无关的,可以脱离Q_FLAG单独使用,事实上这两个宏在Qt4就已经存在(不确定更早是否存在),而Q_FLAG是在Qt5.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的功能。
看一个例子:

namespace MyNamespace
{
Q_NAMESPACE
enum Priority
{High = 1,Low = 2,VeryHigh = 4,VeryLow = 8,
};
Q_ENUM_NS(Priority)         //如不使用Priority,可省略
Q_DECLARE_FLAGS(Prioritys, Priority)
Q_FLAG_NS(Prioritys)
Q_DECLARE_OPERATORS_FOR_FLAGS(Prioritys)
}

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

using namespace MyNamespace;
qDebug()<<(High|Low);
QMetaEnum m = QMetaEnum::fromType<MyNamespace::Prioritys>();  //since Qt5.5
qDebug()<< "keyToValue:"<<m.keyToValue("High|Low");
qDebug()<< "valueToKey:"<<m.valueToKey(High|Low);qDebug()<< "keysToValue:"<<m.keysToValue("High|Low");
qDebug()<< "valueToKeys:"<<m.valueToKeys(High|Low)<<endl;qDebug()<< "isFlag:"<<m.isFlag();
qDebug()<< "name:"<<m.name();
qDebug()<< "enumName:"<<m.enumName();               //since Qt5.12
qDebug()<< "scope:"<<m.scope()<<endl;

运行结果:

QFlags<MyNamespace::Priority>(High|Low)
keyToValue: -1
valueToKey:
keysToValue: 3
valueToKeys: "High|Low" isFlag: true
name: Prioritys
enumName: Priority
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)宏放到定义之前,代码从略。
使用上:

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

对比改进后的:

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

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

结语
很多Qt程序员喜欢用旧版本编程,但是我是坚定的新版本拥趸,在给程序编写带来便利的同时,还能满足自己的好奇心,何乐而不为呢?
 
原文链接:https://blog.csdn.net/qq_36179504/article/details/100895133

Qt中的枚举变量,Q_ENUM,Q_FLAG,Q_NAMESPACE,Q_ENUM_NS,Q_FLAG_NS以及其他相关推荐

  1. Qt中使用多线程的一些心得(一)——继承QThread的多线程使用方法

    一 前言 二Qt多线程方法一 继承QThread 2.1使用多线程的原因分析 2.2 写一个继承于QThread的线程 三 总结 一 前言   本篇文章部分内容参考了该博文:传送门.   Qt中有两种 ...

  2. 如何在QT中读取串口数据

    总是能在别人的博客中学到太多太多,谢谢各位对知识的无私共享,谢谢大家 前言 去年我使用Qt编写串口通信程序时,将自己的学习过程写成了教程(Qt编写串口通信程序全程图文讲解),但是由于时间等原因,我只实 ...

  3. QT 中的 Graphics View 系统

    这个框架是一个相对成熟的渲染引擎的上层框架,通常也可以会叫做 Scene - View. 在QT中,GraphicsView是一个与QWdiget系列一点点不一样的系统.这个系统主要由下面几个框架类构 ...

  4. Qt中的测试 枚举与 QFlags详解

    传统的 C++ 编程中,通常使用整数来保存 enum 的逻辑运算结果 (与.或.非.异或等),在进行逻辑运算的时候没有进行类型检查,一个枚举类型可以和其他的枚举类型进行逻辑运算,运算的结果可以直接传递 ...

  5. QT中串口通信程序(转)

    (说明:我们的编程环境是windows xp下,在Qt Creator中进行,如果在Linux下或直接用源码编写,程序稍有不同,请自己改动.) 在Qt中并没有特定的串口控制类,现在大部分人使用的是第三 ...

  6. QT 中QTimer 和 startTimer()的区别

    最需要注意一点 请注意,QTimer的准确性取决于底层操作系统和硬件.timerType参数允许您自定义计时器的准确性.有关不同计时器类型的信息,请参见Qt::TimerType.大多数平台支持20毫 ...

  7. QT 中使用 OpenCv 的 CascadeClassifier 报错

    问题 在 QT 中调用 OpenCv 的 CascadeClassifier 进行人脸框检测的时候,在构造函数中进行检测器的初始化,随后调用相机读取图片的时候就会报错,报的错误是 Segment Fa ...

  8. 【Qt】Qt中使用ssl时报错:qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method

    一.问题 在Qt中使用https,运行时报错: qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method 二.原因分析 SSLv2由 ...

  9. Qt中两种定时器用法

    在Qt中使用定时器有两种方法,一种是使用QObiect类的定时器:一种是使用QTimer类.定时器的精确性依赖于操作系统和硬件,大多数平台支持20ms的精确度. 1.QObject类的定时器 QObj ...

最新文章

  1. 在ViT、MLP-Mixer等进行4800多次实验,谷歌发现大规模预训练存在瓶颈
  2. java语法结构是什么意思_java - 基础 - 语法结构
  3. 怎样在python代码中输入π_鼠标自动点击、键盘自动输入?几行Python代码搞定
  4. 为什么支付宝不提供房贷业务?原因在这里
  5. mvn exec运行java Main报错
  6. SQL SERVER性能分析--死锁检测数据库阻塞语句
  7. cpio备份linux系统,linux cpio 备份命令
  8. 嵌入式linux 项目开发(一)——HTML编程
  9. linux获取系统启动时间
  10. nginx websocket 代理
  11. 解压软件Bandizip Archiver for mac
  12. 宇电智能调节仪AIBUS 及MODBUS 通讯协议说明V8.0
  13. YLMF OS 发布
  14. 【论文阅读】A Memory-Efficient Deterministic Finite Automaton-Based Bit-Split String Matching Scheme
  15. 重磅!2019上海落户政策大全!
  16. 蜂巢的艺术与技术价值 - PostgreSQL PostGIS's hex-grid
  17. 一建网课一级建造师精讲班视频课件建筑市政机电法规管理经济
  18. python编码的种类以及转换以及bytes数据类型的介绍
  19. linux查看usb文件,linux lsusb查看USB信息
  20. excel2007整体调整行间距

热门文章

  1. 群体智能,多个机器人协同搬运!
  2. 自己动手写CPU(5)简单算术操作指令实现_1
  3. ajax 局部页面替换innerhtml,ajax jquery 页面局部刷新的不同实现代码
  4. andriod studio怎么设置图片大小_Word图片大小总是对不齐,如何统一图片的大小位置,看一眼就会!...
  5. 区块链浏览器_区块链FBI——如何用区块链浏览器追踪链上数据
  6. WebGL(五)——WEBGL缓冲区,绘制三角形
  7. stata主成分分析_主成分分析在STATA中的实现
  8. 移动端去除横向滚动条
  9. linux-free命令
  10. cortex m0启动代码详解