目录
3、Q_NAMESPACE, Q_ENUM_NS 和 Q_FLAG_NS 命名空间
一、引用 枚举值宏 所解决的问题:
1、Q_ENUM
能够轻松完成枚举量和字符串之间的相互转化。
枚举参数显示的是 int 类型,如:3。3 这个信息对于我们 调试或打印日志 很不友好。
在方法内,我们并不知道这个 3 代表的是什么。为了解决这个问题,有了个很有用的特性:Q_ENUM
2、Q_FLAG
弥补 C++ 中结构体 无法组合使用,和 缺乏类型检查 的缺点,可以 拥有多个身份,如:
enum Roles
{
Admin = 1,
Member = 2
}
上述定义了两个身份:Admin 和 Member,如果一个用户想拥有两个身份应该怎么做?
Q_FLAG 就是为了解决这个问题而创造出来的。
二、手写枚举值和字符串之间的相互转化
typedef enum
{
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
} WeekEnum;
1.使用 switch-case 进行装换
char * StringDirectionT(WeekEnum mWeek)
{
switch(mWeek)
{
case SUNDAY : return "SUNDAY";
case MONDAY : return "MONDAY";
case TUESDAY : return "TUESDAY";
case WEDNESDAY : return "WEDNESDAY";
case THURSDAY : return "THURSDAY";
case FRIDAY : return "FRIDAY";
case SATURDAY : return "SATURDAY";
default : printf("Illegal direction value!\n;
}
}
输出:
printf("The Week is %s \n",StringDirectionT(m_Week));
2.使用数组
char *a_Week[] = { "SUNDAY", "MONDAY", "TUESDAY","WEDNESDAY","THURSDAY","FRIDAY","FRIDAY","SATURDAY"};
输出:
然后直接根据vl_Week的值去对应的数组元素
3.使用宏定义
#define enumToStr(WEEEK) "\""#WEEK"\""
输出:
printf("The Week is %s",enumToStr(vl_Week));
三、QT 自带宏命令
Qt 使用一组宏命令来完成枚举量扩展功能的(正如Qt的其他核心机制一样),这些宏包括
Q_ENUM, Q_FLAG, Q_DECLARE_FLAGS,Q_DECLARE_OPERATORS_FOR_FLAGS,
Q_ENUM_NS, Q_FLAG_NS,
这些宏的实现原理和如何展开如何注册到 Qt 内核均不在本文的讲解范围,本文只讲应用。
1、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();
输出是
MyEnum::High MyEnum::Low
keyToValue: 4
valueToKey: VeryHigh
keyCount: 4
使用 Q_ENUM 注册过的枚举类型,可以不加修饰直接被 qDebug() 打印出来,另外通过静态函数 QMetaEnum::fromType() 可以获得一个 QMetaEnum 对象。以此作为中介,能够轻松完成枚举量和字符串之间的相互转化。
这一点恐怕是引入Q_ENUM机制最直接的好处。
除此以外,QMetaEnum 还提供了一个内部的索引,从 1 开始给每个枚举量按自然数顺序编号(注意和枚举量本身的数值是两回事),提供了 int value(int index) 和 const char *key(int index)
两个便捷函数分别返回枚举量对应的数值和枚举量对应的字符串,配合 keyCount() 函数可以实现枚举量的遍历:
qDebug()<<m.name()<<":"; // 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
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 被弃用的原因)。
2、Q_FLAG 的使用
这个宏是为了解决拥有多个身份(枚举值)的问题的。
先看一下其发展路径:
enum Roles
{
Admin = 1,
Member = 2
}
对于上述例子,要表示拥有多个身份,用 | 运算:Roles.Admin | Roles.Member
Roles.Admin | Roles.Member 的值是 3,由于 Roles 枚举类型中并没有定义一个值为 3 的字段,所以在方法内 roles 参数显示的是 3。
第一个问题:
但是这里也带来了一个问题,3 这个值并不在枚举结构 Roles 中,如果函数使用 Roles 作为自变量,编译器是无法通过的,为此往往把函数自变量类型改为整型,但因此也就丢掉了类型检查,输入量有可能是其他无意义的整型量,在运行时带来错误。
Qt引入 QFlags 类,配合一组宏命令完美地解决了这个问题。
QFlags 是一个简单的类,可以装入一个枚举量,并重载了与或非等运算符,使得枚举量能进行与或非运算,且运算结果还是一个 QFlags 包装的枚举量。一个普通的枚举类型包装成 QFlags 型,需要使用Q_DECLARE_FLAGS 宏,在全局任意地方使用”|"操作符计算自定义的枚举量,需要使用 Q_DECLARE_OPERATORS_FOR_FLAGS 宏。
加上这个 Flags 特性后,我们再来调试时,roles 参数的值就会显示为 Admin|Member 了
第二个问题:
到这,枚举类型 Roles 一切看上去没什么问题,但如果现在要增加一个角色:Mananger,会发生什么情况?按照数字值递增的规则,Manager 的值应当设为 3
但是,(Admin | Member) 的值也是 3,这就出现了 枚举值冲突 问题,如何解决呢?
解决枚举值冲突:2 的幂
既然是二进制逻辑运算 “或” 会和成员值产生冲突,那就利用逻辑运算 或 的规律来解决。
我们知道 “或” 运算的逻辑是两边只要出现一个 1 结果就会 1,比如 1|1、1|0 结果都是 1,只有 0|0 的情况结果才是 0。
那么我们就要避免任意两个值在相同的位置上出现 1。根据二进制满 2 进 1 的特点,只要保证枚举的各项值都是 2 的幂即可。
比如:
1: 00000001
2: 00000010
4: 00000100
8: 00001000
再往后增加的话就是 16、32、64...,其中各值不论怎么相加都不会和成员的任一值冲突。这样问题就解决了,所以我们要这样定义 Roles 枚举的值:
enum Roles
{
Admin = 1,
Member = 2,
Manager = 4,
Operator = 8
}
不过在定义值的时候要在心中小小计算一下,如果你想懒一点,可以用下面这种“位移”的方法来定义:
enum Roles
{
Admin = 1 << 0,
Member = 1 << 1,
Manager = 1 << 2,
Operator = 1 << 3
}
一直往下递增编值即可,阅读体验好,也不容易编错。
两种方式是等效的,常量位移的计算是在编译的时候进行的,所以相比不会有额外的开销。
看一下完整示例
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) //实际上是 QFlags 的定义式,之后才能使用 Q_FLAG(Flags)把定义的Flags注册到元对象系统
Q_FLAG(OrientationFlags)
};
// 赋予了Flags一个全局操作符“|”,没有这个宏语句,Flags量之间进行与操作后的结果将是一个int值,而不是Flags值
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 就好了。
3、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,这套系统虽然已经不建议使用,但是为了兼容性,还是予以保留
本文介绍了Qt中枚举变量的高级技巧,包括Q_ENUM和Q_FLAG宏的作用,以及如何手动实现枚举值与字符串的转换。详细讲解了Q_ENUM的使用场景和注意事项,Q_FLAG解决的多枚举值组合问题,以及Qt自带的Q_NAMESPACE, Q_ENUM_NS和Q_FLAG_NS在命名空间中的应用。最后总结了Qt枚举扩展的演变和发展。"
110738870,10296037,OpenCV Python: 将白色像素转为黑色像素的图像处理,"['图像处理', 'OpenCV', 'Python编程']

被折叠的 条评论
为什么被折叠?



