Content Table

枚举与 QFlags

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

Qt 中,模板类 QFlags<Enum> 提供了类型安全的方式保存 enum 的逻辑运算结果解决上面的这几个问题,这种方式在 Qt 里很常见,例如设置 QLabel 对齐方式的函数是 QLabel::setAlignment(Qt::Alignment) (typedef QFlags<Qt::AlignmentFlag> Qt::Alignment),这就意味着传给 setAlignment 的参数只能是枚举 Qt::AlignmentFlag 的变量、它们的逻辑运算结果或者 0,如果传入其他的枚举类型或者非 0 值,编译时就会报错:

1
2
3
4
label->setAlignment(0); // OK
label->setAlignment(Qt::AlignLeft | Qt::AlignTop); // OK

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

使用 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <QDebug>

// 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 的变量即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <QDebug>
#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;
}