在 C++ 中对布尔值使用位运算符

Posted

技术标签:

【中文标题】在 C++ 中对布尔值使用位运算符【英文标题】:Using bitwise operators for Booleans in C++ 【发布时间】:2010-09-06 16:12:52 【问题描述】:

在 C++ 中是否有任何理由不使用位运算符 &、| 和 ^ 来表示“布尔”值?

我有时会遇到我希望两个条件之一为真(XOR)的情况,所以我只是将 ^ 运算符放入条件表达式中。我有时还希望评估条件的所有部分是否为真(而不是短路),所以我使用 & 和 |。有时我还需要累积布尔值,&= 和 |= 非常有用。

执行此操作时,我引起了一些人的注意,但代码仍然比其他情况下更有意义且更简洁。有什么理由不将这些用于布尔值吗?是否有任何现代编译器对此给出不好的结果?

【问题讨论】:

如果您在提出这个问题后已经充分了解 C++,那么您应该返回并取消接受当前答案,并接受 Patrick Johnmeyer 的答案,以正确解释短路差异。 一般性评论,因为人们质疑这是否是一个好主意。如果您关心单元测试的分支覆盖率,那么减少分支是可取的。位运算符对此很有用。 【参考方案1】:

对 bool 使用按位运算有助于节省处理器不必要的分支预测逻辑,这是由逻辑运算引入的“cmp”指令产生的。

用按位运算(所有操作数都是布尔值)替换逻辑会生成更高效的代码,提供相同的结果。理想情况下,效率应该超过使用逻辑操作在排序中可以利用的所有短路优势。

这会使代码有点不可读,尽管程序员应该注释它并说明这样做的原因。

【讨论】:

【参考方案2】:

||&& 是布尔运算符,内置运算符保证返回truefalse。没有别的了。

|&^ 是按位运算符。当你操作的数字域只是 1 和 0 时,它们是完全相同的,但是在你的布尔值不是严格的 1 和 0 的情况下——就像 C 语言的情况——你最终可能会出现一些行为你不想要。例如:

BOOL two = 2;
BOOL one = 1;
BOOL and = two & one;   //and = 0
BOOL cand = two && one; //cand = 1

然而,在 C++ 中,bool 类型保证仅为truefalse(分别隐式转换为10),因此不必担心这种立场,但人们不习惯在代码中看到这样的事情这一事实为不这样做提供了一个很好的论据。只需说 b = b && x 即可。

【讨论】:

在 C++ 中没有逻辑异或运算符 ^^ 这样的东西。对于两个参数作为 bools != 做同样的事情,但如果任何一个参数都不是 bool 则可能是危险的(就像使用 ^ 进行逻辑异或一样)。不确定这是如何被接受的...... 回想起来,这有点愚蠢。无论如何,将示例更改为 && 以解决问题。我发誓我过去曾经使用过 ^^,但一定不是这样。 C 有一个 bool 类型,自 10 年以来只能是 0 或 1。 _布尔 即使对于布尔值,它们仍然不一样,因为它们不会短路 是的,我坚信这不应该是公认的答案,除非您将“当您操作的数字域只是 1 和 0,那么它们完全相同”更改为“当域您操作的数字只有 1 和 0,唯一的区别是按位运算符不会短路”。前一种说法是完全错误的,所以同时我对这个答案投了反对票,因为它包含那句话。【参考方案3】:

位级运算符的缺点。

你问:

“有什么理由不使用位运算符&|^ 来表示C++ 中的“布尔”值吗? ”

是的,逻辑运算符,即内置的高级布尔运算符!&&||,具有以下优点:

保证参数转换bool,即到01序数值。

保证短路评估,一旦知道最终结果,表达式评估就会停止。 这可以解释为树值逻辑,具有 TrueFalseIndeterminate

可读的文本等价物 notandor,即使我自己不使用它们。 正如读者 Antimony 在评论中指出的那样,位级运算符也有替代标记,即 bitandbitorxorcompl,但在我看来,它们的可读性不如 andor 和 @ 987654339@.

简单地说,高级运算符的每一个优点都是位级运算符的缺点。

特别是,由于按位运算符缺少到 0/1 的参数转换,因此您会得到例如1 & 20,而1 && 2true。还有^,按位排他或,可能会以这种方式行为不端。视为布尔值1和2相同,即true,但视为位模式它们是不同的。


如何在 C++ 中表达逻辑either/or

然后你为这个问题提供一点背景,

“我有时会遇到我希望两个条件之一为真 (XOR) 的情况,所以我只是将 ^ 运算符放入条件表达式中。”

嗯,位运算符比逻辑运算符具有更高的优先级。这尤其意味着在混合表达式中,例如

a && b ^ c

你可能会得到意想不到的结果a && (b ^ c)

改为只写

(a && b) != c

更简洁地表达你的意思。

对于多参数either/or,没有 C++ 运算符可以完成这项工作。例如,如果你写a ^ b ^ c,那么这不是一个表示“abc 都是真的”的表达式。相反,它说,“奇数个 abc 是真的”,可能是其中 1 个或全部 3 个......

abcbool 类型时,要表达一般的非此即彼,只需写

(a + b + c) == 1

或者,使用非bool 参数,将它们转换为bool

(!!a + !!b + !!c) == 1


使用&= 来累积布尔结果。

您进一步详细说明,

“有时我还需要累积布尔值,&=|=? 会很有用。”

嗯,这对应于检查是否分别满足 allany 条件,de Morgan's law 告诉你如何从一个开始给对方。 IE。你只需要其中之一。原则上,您可以将*= 用作&&= 运算符(正如老乔治布尔发现的那样,逻辑与可以很容易地表示为乘法),但我认为这会使代码维护者感到困惑甚至误导。

同时考虑:

struct Bool

    bool    value;

    void operator&=( bool const v )  value = value && v; 
    operator bool() const  return value; 
;

#include <iostream>

int main()

    using namespace std;

    Bool a  = true;
    a &= true || false;
    a &= 1234;
    cout << boolalpha << a << endl;

    bool b = true;
    b &= true || false;
    b &= 1234;
    cout << boolalpha << b << endl;

使用 Visual C++ 11.0 和 g++ 4.7.1 输出:

真的 错误的

结果不同的原因是位级别&amp;= 没有提供其右侧参数到bool 的转换。

那么,您希望使用&amp;= 获得哪些结果?

如果是前者,true,那么最好定义一个运算符(例如,如上所述)或命名函数,或者使用右侧表达式的显式转换,或者完整地编写更新。

【讨论】:

位运算符也有文本等价物 @Antimony:起初我不明白那个评论,但是是的,位级操作有替代标记bitandbitorxorcompl。我认为这就是我使用“可读”这个资格的原因。当然,例如的可读性compl 42 是主观的。 ;-) 据我所知,逻辑运算符 可以 被其他参数类型重载(尽管它们通常不是),所以第一点“保证将参数转换为 @987654379 @" 只是约定的结果,并不能保证 C++ 语言实际上给了你。 @MarcvanLeeuwen:您的视觉子系统可能没有注意到“内置的高级布尔运算符!、&& 和 ||”。你是对的,这些运算符可以重载,然后一个主要问题是语义发生了微妙的变化,不再是短路评估。但这是重载的问题,而不是内置运算符。 没错(我确实错过了)。但是这种说法仍然具有误导性,因为您只是说这些运算符采用布尔参数;如果(且仅当)选择了运算符的内置版本,则编译器将插入参数的转换。同样,内置(高级)无符号整数加法运算符+ 保证将其参数转换为unsigned,但这并不妨碍unsigned int n=-1; n+=3.14; 实际使用double(或者是float?)加法运算。 (比较您的 &amp;= 示例。)【参考方案4】:

两个主要原因。总之,慎重考虑;这可能是有充分理由的,但如果你的 cmets 中有非常明确的内容,因为它可能很脆弱,而且正如你自己所说,人们通常不习惯看到这样的代码。

按位异或!= 逻辑异或(0 和 1 除外)

首先,如果您对falsetrue(或01,作为整数)以外的值进行操作,^ 运算符可以引入不等同于逻辑异或的行为。例如:

int one = 1;
int two = 2;

// bitwise xor
if (one ^ two)

  // executes because expression = 3 and any non-zero integer evaluates to true


// logical xor; more correctly would be coded as
//   if (bool(one) != bool(two))
// but spelled out to be explicit in the context of the problem
if ((one && !two) || (!one && two))

  // does not execute b/c expression = ((true && false) || (false && true))
  // which evaluates to false

感谢用户@Patrick 首先表达了这一点。

操作顺序

其次,|&amp;^,作为位运算符,不要短路。此外,在单个语句中链接在一起的多个按位运算符(即使带有显式括号)也可以通过优化编译器重新排序,因为所有 3 个操作通常都是可交换的。如果操作的顺序很重要,这一点很重要。

换句话说

bool result = true;
result = result && a() && b();
// will not call a() if result false, will not call b() if result or a() false

不会总是给出与

相同的结果(或结束状态)
bool result = true;
result &= (a() & b());
// a() and b() both will be called, but not necessarily in that order in an
// optimizing compiler

这一点尤其重要,因为您可能无法控制方法 a()b(),或者其他人可能会在不了解依赖关系的情况下出现并更改它们,并导致令人讨厌的(通常仅发布-构建)错误。

【讨论】:

如果你在一种情况下转换为 bool 而不是在另一种情况下,这并不是一个公平的比较......在这两种情况下转换为 bool 是什么使它工作,以及为什么它是脆弱的(因为你必须记住它)。 短路的解释是为什么这绝对应该是公认的答案;帕特里克的回答(错误......另一个帕特里克,刚刚命名为帕特里克)完全错误地说“当你操作的数字域只是 1 和 0,那么它们是完全相同的”【参考方案5】:

与 Patrick 的回答相反,C++ 没有用于执行短路异或的 ^^ 运算符。如果你想一想,拥有^^ 运算符无论如何都没有意义:使用异或,结果总是取决于两个操作数。然而,在比较 1 &amp; 21 &amp;&amp; 2 时,Patrick 关于非bool“布尔”类型的警告同样适用。一个典型的例子是 Windows GetMessage() 函数,它返回三态 BOOL:非零、0-1

使用&amp; 代替&amp;&amp;| 代替|| 并不少见,因此,如果您是故意这样做的,则值得评论说明原因。

【讨论】:

无论是否考虑短路,^^ 运算符仍然有用。它会 a) 在布尔上下文中评估操作数 b) 保证返回 1 或 0。 @CraigMcQueen。定义inline bool XOR(bool a,bool b) return a!=b; 并得到你想要的很简单,除了它是一个函数而不是一个(中缀)运算符。或者您可以直接使用!= 或重载其他具有此含义的运算符,但是当然您需要非常小心,以免意外地使用同名的意外重载。顺便说一句,||&amp;&amp; 返回 truefalse,而不是 10 而且似乎!||&amp;&amp; 在布尔上下文中评估它们的参数只是因为这些运算符很少被重载以接受其他类型;据我所知,该语言确实允许这种重载,因此不保证在布尔上下文中评估参数。【参考方案6】:

IIRC,许多 C++ 编译器在尝试将按位运算的结果转换为布尔值时会发出警告。您必须使用类型转换才能使编译器满意。

在 if 表达式中使用按位运算会引起同样的批评,尽管编译器可能不会。任何非零值都被认为是真的,所以像“if (7 & 3)”这样的东西将是真的。这种行为在 Perl 中可能是可以接受的,但 C/C++ 是非常明确的语言。我认为斯波克眉毛是尽职调查。 :) 我会附加“== 0”或“!= 0”以完全清楚您的目标是什么。

但无论如何,这听起来像是个人喜好。我会通过 lint 或类似工具运行代码,看看它是否也认为这是一个不明智的策略。就个人而言,它读起来像是一个编码错误。

【讨论】:

您的帖子促使我对此进行测试,而 gcc 没有警告!太糟糕了,因为我打算使用该警告来证明让我运行 &= 来累积 bool 结果是合理的,因为我相信如果其他人以后更改我的测试,他们会看到警告。我的代码即使是整数也会失败,因为它们评估为 0/false - 没有警告!是时候重构了……【参考方案7】:

我觉得

a != b

是你想要的

【讨论】:

这是真的,+1。但是该运算符没有分配版本(可以说是!==),因此如果您正在计算一系列bool 值的异或,则需要在循环体中写入acc = acc!= condition(i);。编译器可能可以像存在!== 一样有效地处理这个问题,但有些人可能会发现它看起来不太好,并且更喜欢将布尔值添加为整数,然后测试总和是否为奇数。【参考方案8】:

Patrick 提出了很好的观点,我不再赘述。但是,我是否建议尽可能使用命名良好的布尔变量将“if”语句减少为可读的英语。例如,这是使用布尔运算符,但您同样可以按位使用并适当地命名布尔值:

bool onlyAIsTrue = (a && !b); // you could use bitwise XOR here
bool onlyBIsTrue = (b && !a); // and not need this second line
if (onlyAIsTrue || onlyBIsTrue)

 .. stuff ..

您可能认为使用布尔值似乎没有必要,但它有助于两件主要事情:

您的代码更容易理解,因为“if”条件的中间布尔值使条件的意图更加明确。 如果您使用非标准或意外代码,例如布尔值的按位运算符,人们可以更轻松地了解您这样做的原因。

编辑:你没有明确说你想要'if'语句的条件(尽管这似乎最有可能),这是我的假设。但我对中间布尔值的建议仍然有效。

【讨论】:

【参考方案9】:

扬起的眉毛应该足以告诉您停止这样做。您不是为编译器编写代码,而是先为其他程序员编写代码,然后再为编译器编写代码。即使编译器工作正常,也不是您想要让其他人感到惊讶 - 位运算符用于位操作而不是布尔值。 我想你也用叉子吃苹果?它有效,但它会让人们感到惊讶,所以最好不要这样做。

【讨论】:

是的,这应该是最佳答案。遵循最小意外原则。做不寻常事情的代码更难阅读和理解,而且代码的阅读频率远高于编写频率。不要使用这种可爱的技巧。 我来这里是因为我的同事使用按位“与”运算符作为布尔值。现在我花时间试图了解这是否正确。如果他不这样做,我现在会做一些更有用的事情-_-。如果您重视同事的时间,请不要对 bool 使用按位运算符!

以上是关于在 C++ 中对布尔值使用位运算符的主要内容,如果未能解决你的问题,请参考以下文章

第一章 数据储存 1.1 位与位存储

为啥要在 C# 中对布尔值使用 |= 运算符?

位运算,处理前台多选值

运算符

布尔类型是啥意思?

如何在 linux 中对文件执行按位运算?