如何表示状态标志以及如何使用按位或来处理位标志?

How are state flags represented and how bitwise OR is used to work with bit flags?

如果我们打开一个文件进行读取,我们可以定义一个或多个状态标志, 例如:ios::out 以及 ios::out | iso::app

我读到了按位或,以及它如何"merges"两个位集,

例如:1010 | 0111 = 1111

话虽这么说,但我不明白它是如何工作的 "behind the scenes" 当我们使用 ifstream.open(filename, stateflagA | stateflagB | stateflagC) 等方法时。

有人可以详细说明这些状态标志的内部工作原理及其内存表示吗?

编辑: 为了更加强调我试图理解的内容(如果有帮助的话), 我假设 open 方法可以接收一个或多个状态标志作为签名中的单独参数,而不是由按位或分隔,所以我想了解按位或如何在这些状态标志上工作以产生不同的最终状态结合几个标志,因此允许我只使用一个参数作为一个状态标志或一组状态标志。 即:

ifstream.open(filename, stateflagA | stateflagB | stateflagC)

而不是

ifstream.open(filename, stateflagA , stateflagB , stateflagC)

"Behind the scene",在计算机的内存中,每一条信息最终都编码为一组比特。您的 CPU 可以对此类基本信息执行基本的 binary algebra 运算(AND、OR、XOR、NOT)。

C++ 运算符 | &^ 只允许直接访问任何整型上的这些 CPU 操作。对于标志管理,明智的做法是使用无符号整数类型,例如 unsigned intunsigned char

快速概述:

  • 诀窍是每个标志对应一个固定位。这通常使用 2 的幂常数来完成(例如:1、2、4、8,二进制编码为 0001、0010、0100 和 1000)。
  • 命名常量是因为它比使用文字更清晰(例如:const unsigned FlagA=1, FlagB=2, FlagC=4;
  • binary AND x & y 确保只有 x 和 y 中都为 1 的位保持为 1。因此,这用于通过 "anding" 将标志重置为标志为 0 的值。所以x & FlagB 重置除标志 B 之外的所有标志
  • binary OR x | y x 或 y 中为 1 的任何位都变为 1。因此它用于设置标志。示例:x | FlagB 设置标志 B.
  • 二进制 AND 也是检查标志是否已设置的快速方法:当且仅当标志 B 已设置时,(x & FlagB) 才为真。

编辑: 关于您关于 ifstream::open() 参数的具体问题:为方便起见,这是一个设计选择。如您所见,有 6 个标志影响文件的处理方式(其中一些很少使用)。因此,不是每次都提供 6 个标志中的每一个,标准决定您将以开放模式组合提供它们。可变数量的参数不是替代方案,因为被调用函数必须知道您提供了多少参数。

如果我们采用 GNU libstdc++ 实现并查看它们的实际实现方式,我们会发现:

enum _Ios_Openmode 
{ 
  _S_app        = 1L << 0,
  _S_ate        = 1L << 1,
  _S_bin        = 1L << 2,
  _S_in         = 1L << 3,
  _S_out        = 1L << 4,
  _S_trunc      = 1L << 5,
  _S_ios_openmode_end = 1L << 16 
};

然后将这些值用作:

typedef _Ios_Openmode openmode;

static const openmode app =     _S_app;

/// Open and seek to end immediately after opening.
static const openmode ate =     _S_ate;

/// Perform input and output in binary mode (as opposed to text mode).
/// This is probably not what you think it is; see
/// http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt11ch27s02.html
static const openmode binary =  _S_bin;

/// Open for input.  Default for @c ifstream and fstream.
static const openmode in =      _S_in;

/// Open for output.  Default for @c ofstream and fstream.
static const openmode out =     _S_out;

/// Open for input.  Default for @c ofstream.
static const openmode trunc =   _S_trunc;

由于值被选择为 1 << n,它们恰好是一个 "bit",这允许我们组合然后使用 |(或)——以及其他类似的操作.

所以app在二进制中是0000 0001bin0000 0100,所以如果我们将app | bin作为打开文件的模式,我们得到 0000 0101fstream 的实现内部可以使用

 if (mode & bin) ... do stuff for binary file ... 

 if (mode & app) ... do stuff for appending to the file ...

其他 C++ 库实现可能会为每个标志选择一组不同的位值,但会使用类似的系统。

位标志的表示方式与所有整数值的表示方式完全相同。使它们 "flags" 的原因是您的程序对它们的值的 解释

位标志用于小型值集的紧凑表示。每个值都分配有一个位索引。该索引处的位设置为 1 的所有整数都被解释为包含相应成员的集合。

考虑一个小例子:假设我们需要表示一组三种颜色 - 红色、绿色和蓝色。我们为红色分配索引 0,为绿色和索引分配 1,为蓝色分配索引 2。这对应于以下表示:

BINARY DECIMAL COLOR
------ ------- -----
   001       1  Red
   010       2  Green
   100       4  Blue

请注意,每个标志都是 2 的幂。这是 属性 的二进制数,其中一位设置为 1。这是它在 C++ 中的样子:

enum Color {
    Red   = 1 << 0
,   Green = 1 << 1
,   Blue  = 1 << 2
};

1 << n 是构造整数的标准方法,其中 n 位置的一位设置为 1.

有了这个表示,我们可以构建具有这些颜色的任意组合的集合:

BINARY DECIMAL COLOR
------ ------- -----
   001       1  Red
   010       2  Green
   011       3  Red+Green
   100       4  Blue
   101       5  Blue+Red
   110       6  Blue+Green
   111       7  Blue+Green+Red

这就是位操作发挥作用的时候了:我们可以使用它们在单个操作中构造集合并检查成员资格。

例如,我们可以构造一组 RedBlue 与一个 | 像这样:

Color purple = Red | Blue;

在幕后,这一切所做的就是将 5 分配给 purple,因为 4 | 15。但是由于您的程序将 5 解释为一组两种颜色,因此 5 的含义与表示事物数量的整数 5 的含义不同一个袋子。

您可以通过对其应用 & 来检查集合是否具有特定成员:

if (purple & Red) {
    // returns true
}
if (purple & Green) {
    // returns false
}

I/O 库使用的标志以相同的方式工作。一些标志组合起来产生 位掩码 。它们的工作方式与单个标志相同,但不是让您找到成员资格,而是让您在单个位操作中找到集合交集:

Color yellow = Blue | Green;
Color purple = Red | Blue;
Color common = yellow & purple; // common == Blue