传统的 C++ 编程中,通常使用整数来保存 enum 的逻辑运算结果 (与、或、非、异或等),在进行逻辑运算的时候没有进行类型检查,一个枚举类型可以和其他的枚举类型进行逻辑运算,运算的结果可以直接传递给接收参数为整数的函数。
Qt 中,模板类 QFlags<Enum> 提供了类型安全的方式保存 enum 的逻辑运算结果解决上面的这几个问题,这种方式在 Qt 里很常见,例如设置 QLabel 对齐方式的函数是 QLabel::setAlignment(Qt::Alignment) (typedef QFlags<Qt::AlignmentFlag> Qt::Alignment),这就意味着传给 setAlignment 的参数只能是枚举 Qt::AlignmentFlag 的变量、它们的逻辑运算结果或者 0,如果传入其他的枚举类型或者非 0 值,编译时就会报错:
label->setAlignment(0);
label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
label->setAlignment(Qt::WA_Hover); // Error: 编译时报错
想要把我们定义的枚举类型和 QFlags 一起使用,需要用到两个宏:
Q_DECLARE_FLAGS(Flags, Enum):- Enum 是已经定义好的枚举类型
- 展开的结果为
typedef QFlags<Enum> Flags
Q_DECLARE_OPERATORS_FOR_FLAGS(Flags):- Flags 就是类型
QFlags<Enum> - 给 Flags 定义了运算符
|,使得 Enum 和 Enum,Enum 和 Flags 能够使用或运算符|,结果为 Flags
- Flags 就是类型
使用 QFlags 时需要留意以下几点:
- QFlags 其实就是用于位操作,设置它保存的数值的某一位为 1 或者为 0,所以和 QFlags 一起使用的枚举类型,其变量的值需要是 2 的 n 次方,即 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, …,它们的特点是其二进制位中只有一位是 1,其他位全部为 0,这样每一个枚举值只会和 QFlags 中的某一个位对应,不会出现交叉的情况
- 调用函数
QFlags::setFlag(Enum flag, bool on = true),on 为 true 时设置 flag 对应的位为 1,on 为 false 时设置 flag 对应的位为 0,设置 flag 对应的位为 1 还可以使用运算符| - 调用函数
QFlags::testFlag(Enum flag)测试 flag 对应的位是否为 1 - 整数转为 QFlags: 把整数作为 QFlags 构造函数的参数创建一个 QFlags 变量
- QFlags 转为整数: 调用
int(flags)把 QFlags 变量转换为整数值
下面就演示一下怎么使用 QFlags:
// Flag 的变量值是 2^n, 每个值的二进制只有一个位是 1,其他全为 0
enum class Flag {
Js = 0x01, // 1 : 0000 0001
Go = 0x02, // 2 : 0000 0010
Cpp = 0x04, // 4 : 0000 0100
Php = 0x08, // 8 : 0000 1000
Java = 0x10, // 16 : 0001 0000
Scala = 0x20, // 32 : 0010 0000
};
Q_DECLARE_FLAGS(Flags, Flag)
Q_DECLARE_OPERATORS_FOR_FLAGS(Flags) // 使得 Flag 和 Flag,Flag 和 Flags 能够使用或运算符 |,结果为 Flags
int main() {
Flags flags(0x08); // int to Flags
flags |= Flag::Js; // 添加 Js
flags |= Flag::Go; // 添加 Go
flags.setFlag(Flag::Cpp, true); // 添加 Cpp
flags.setFlag(Flag::Cpp, false); // 删除 Cpp
qDebug() << flags; // 输出: QFlags(0x1|0x2|0x8)
qDebug() << flags.testFlag(Flag::Js); // 输出: true
qDebug() << flags.testFlag(Flag::Cpp); // 输出: false
qDebug() << int(flags); // 输出: 11 (Flags to int)
flags = Flag::Java | Flag::Scala; // 使用 | 同时设置多个 flag
qDebug() << flags; // 输出: QFlags(0x10|0x20)
// flags = Flag::Java | Qt::AlignLeft; // Error: no viable overloaded =
return 0;
}
在日常开发中,权限也可以使用 QFlags 来实现,下面只演示 3 种权限的使用,实际开发中根据具体业务需求修改枚举类型 Permission 的变量即可:
#include <QMap>
#include <QStringList>
// 权限
enum class Permission {
Readable = 0x01,
Writable = 0x02,
Excutable = 0x04,
};
Q_DECLARE_FLAGS(Permissions, Permission)
Q_DECLARE_OPERATORS_FOR_FLAGS(Permissions)
// 权限的值和其对应的 label 的 map
QMap<Permission, QString> PermissionLabelMap {
{ Permission::Readable, "可读" },
{ Permission::Writable, "可写" },
{ Permission::Excutable, "可执行" },
};
// 用户
class User {
public:
User(const QString &username, int psValue) {
this->username = username;
this->ps = Permissions(psValue);
}
bool hasPermission(Permission p) const {
return ps.testFlag(p);
}
void addPermission(Permission p) {
ps.setFlag(p, true);
}
void addPermissions(Permissions ps) {
this->ps |= ps;
}
void removePermission(Permission p) {
ps.setFlag(p, false);
}
void removePermissions(Permissions ps) {
this->ps &= Permissions(~(int(ps)));
}
Permissions getPermissions() const {
return ps;
}
QStringList getPermissionLabels() const {
QStringList labels;
for (auto i = PermissionLabelMap.cbegin(); i != PermissionLabelMap.cend(); ++i) {
if (ps.testFlag(i.key())) {
labels << i.value();
}
}
return labels;
}
private:
Permissions ps;
QString username;
};
int main() {
User user("Bob", 0);
user.addPermissions(Permission::Readable | Permission::Excutable);
qDebug() << user.getPermissions(); // 输出: QFlags(0x1|0x4)
qDebug() << user.getPermissionLabels(); // 输出: ("可读", "可执行")
qDebug() << user.hasPermission(Permission::Readable); // 输出: true
qDebug() << user.hasPermission(Permission::Writable); // 输出: false
user.addPermission(Permission::Writable);
qDebug() << user.getPermissions(); // 输出: QFlags(0x1|0x2|0x4)
qDebug() << int(user.getPermissions()); // 输出: 7
user.removePermissions(Permission::Readable | Permission::Excutable);
qDebug() << user.getPermissionLabels(); // 输出: ("可写")
return 0;
}
又例如:
class CComGeneralTableWidget : public QTableWidget
{
Q_OBJECT
public:
CComGeneralTableWidget(QWidget *parent = Q_NULLPTR);
~CComGeneralTableWidget();
/* 显示属性枚举(注意:id、设计id、可见性、显示名是所有元件都有且必须在属性对话框中显示的,这里就不再列出了
,这里仅仅是列出有些元件需要显示的,有些元件不需要显示的,通过枚举值来控制哪些显示,哪些不显示)
,枚举值必须为0、2的N次方(N >= 0)
*/
enum class EmShowProp
{
eNotShowAllProp = 0, // 画笔颜色、画笔宽度、画刷颜色、线型、旋转信息都不显示
eShowRotateProp = 1, // 显示旋转信息
eShowPenColorProp = 2, // 显示画笔颜色
eShowPenWidthProp = 4, // 显示画笔宽度
eShowBrushColorProp = 8, // 显示画刷颜色
eShowLineTypeProp = 16, // 显示线型
// 显示所有
eShowAllProp = eShowRotateProp | eShowPenColorProp | eShowPenWidthProp | eShowBrushColorProp | eShowLineTypeProp
};
Q_DECLARE_FLAGS(EmShowProps, EmShowProp)
....// 类的其它代码
private:
EmShowProps m_eShowProp{ EmShowProp::eShowAllProp };
};
Q_DECLARE_OPERATORS_FOR_FLAGS(CComGeneralTableWidget::EmShowProps)
注意:通过上述Q_DECLARE_FLAGS声明后, 定义枚举不再是EmShowProp(最原始的用enum声明的、宏的第二个参数)而是 EmShowProps(宏的第一个参数),如果还用enum声明的枚举名定义枚举变量,则会报如下错误:
error C2228: “.testFlag”的左边必须有类/结构/联合
本文详细介绍了Qt中的QFlags在C++编程中的应用,如何通过Q_DECLARE_FLAGS和Q_DECLARE_OPERATORS_FOR_FLAGS提升枚举类型的逻辑运算安全性,并展示了如何使用QFlags实现权限管理和位操作,包括权限的定义、操作和示例。
136

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



