有什么理由不对C ++中的"布尔"值使用按位运算符&,|和^?
有时,我遇到两种情况之一,即我想完全满足两个条件之一(XOR),因此我只是将^运算符放入条件表达式中。 有时我还希望评估条件的所有部分,无论结果是否正确(而不是短路),因此我使用&和|。 有时我还需要累积布尔值,并且&=和| =可能非常有用。
在执行此操作时,我有些挑剔,但是代码仍然比其他方法有意义且更简洁。 有没有理由不将其用于布尔? 是否有任何现代编译器为此带来不好的结果?
如果从提出此问题以来您已经对C ++有了足够的了解,那么您应该返回并接受当前答案,并接受Patrick Johnmeyers答案,以正确解释短路差异。
总的说来,因为人们质疑这是否是一个好主意。 如果您关心单元测试的分支覆盖范围,那么减少分支是可取的。 按位运算符对此很有用。
||和&&是布尔运算符,并且保证内置运算符将返回true或false。没有其他的。
|,&和^是按位运算符。当您操作的数字域仅是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类型只能是true或false(它们分别隐式地转换为1和0),因此从这种立场出发,您不必担心,但是人们不习惯在代码中看到这样的事实,为不这样做提供了很好的理由。只需说b = b && x并完成它。
在C ++中,没有逻辑异或运算符^^这样的东西。对于两个参数,布尔值!=都具有相同的作用,但是如果两个参数都不是布尔值,则可能会很危险(因为将^用于逻辑xor)。不知道如何被接受...
回想起来,那真是愚蠢。无论如何,将示例更改为&&以解决问题。我发誓我过去曾经使用过^^,但事实并非如此。
感谢您重新解决该问题;删除了我的评论。
自以来,C的布尔类型只能为0或1。 _Bool
无论如何。我认为!! two&!! one将解决第一个问题,且= 0。 !将任何非零的东西变成1。
帕特里克(Patrick),因为您已经接受了答案,所以如果您要添加有关使用&=和| =进行累加的信息,那将是很好的。阅读完整个线程并进行了一些测试之后,我检查并了解到gcc在bool passed_tests_ = true; passed_tests_ &= 8;之后给出了false,这意味着我的测试之一很脆弱,无法稍后编辑...(寻找这就是将我带到这里的原因)
即使对于傻瓜来说,它们也不一样,因为它们不会短路
是的,我坚信只有在将"当您操作的数字域仅为1和0时,它们完全相同"更改为"当您正在操作的数字域仅为1时",这才是不可接受的答案。和0,唯一的区别是按位运算符不会短路。"前一个陈述完全是错误的,因此与此同时,我对这个答案不满意,因为它包含了那句话。
两个主要原因。简而言之,请仔细考虑。这样做可能有充分的理由,但是如果您的注释中有非常明确的内容,因为它可能很脆弱,而且就像您自己说的那样,人们通常不习惯看到这样的代码。
按位异或!=逻辑异或(0和1除外)
首先,如果要对false和true(或0和1,作为整数)以外的值进行运算,则^运算符可以引入不等同于逻辑xor的行为。例如:
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首次表达了这一点。
操作顺序
其次,|,&和^作为按位运算符,不会短路。此外,可以通过优化编译器对在单个语句中链接在一起的多个按位运算符(即使带有显式括号)进行重新排序,因为所有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都是使其工作的原因,以及它为什么脆弱的原因(因为您必须记住它)。
短路的解释是为什么这绝对应该成为公认的答案;为什么?帕特里克(Patricks)的回答(错误...另一位叫帕特里克(Patrick)的帕特里克(Patrick))完全错误地说"当您操作的数字的域只有1和0时,它们是完全相同的"
我认为
a != b
是你想要的
的确是+1。但是没有此运算符的分配版本(可以说是!==),因此,如果要计算bool值序列的XOR,则需要在循环主体中写入acc = acc!= condition(i);。编译器可能可以像处理!==一样有效地处理此问题,但有些人可能会发现它看起来不太好,并且倾向于将布尔值添加为整数,然后测试总和是否为奇数。
扬起的眉毛应该告诉你足以停止这样做。您无需为编译器编写代码,而是先为其他程序员编写代码,然后再为编译器编写代码。即使编译器正常工作,也不会让其他人感到惊讶-按位运算符是针对位操作而不是布尔值。
我想你也用叉子吃苹果吗?它有效,但它会让人们感到惊讶,因此最好不要这样做。
是的,这应该是最佳答案。遵循最少惊讶的原则。做异常事情的代码更难阅读和理解,而且阅读的频率远高于编写的频率。不要使用像这样的可爱技巧。
我在这里是因为我的同事对布尔使用按位"和"运算符。现在,我正在花时间尝试了解这是否正确。如果他不这样做,我现在会做些更有用的事情-_-。如果您珍惜同事的时间,请不要对布尔使用按位运算符!
位级运算符的缺点。
你问:
Is there any reason not to use the bitwise operators &, |, and ^ for"bool" values in C++?
是的,逻辑运算符(即内置的高级布尔运算符!,&&和||)具有以下优点:
确保将参数转换为bool,即转换为0和1序数值。
保证短路评估,一旦知道最终结果,表达式评估就会停止。
这可以解释为具有True,False和Indeterminate的树值逻辑。
可读的文本等效项not,and和or,即使我自己不使用它们也是如此。
正如读者Antimony在评论中指出的那样,位级运算符还具有其他令牌,即bitand,bitor,xor和compl,但在我看来,它们比and,or和< 5233>。
简而言之,高级运算符的每个此类优点都是位级运算符的缺点。
特别是,由于按位运算符缺少将参数转换为0/1的结果,因此例如1 & 2 0,而1 && 2 true。按位异或的^也会以这种方式出现错误。布尔值1和2相同,即true,但被视为位模式却有所不同。
如何在C ++中表达逻辑或。
然后,您为问题提供了一些背景知识,
I sometimes run into situations where I want exactly one of two conditions to be true (XOR), so I just throw the ^ operator into a conditional expression.
好吧,按位运算符的优先级高于逻辑运算符。这尤其意味着在诸如
a && b ^ c
您会得到可能意外的结果a && (b ^ c)。
而是只写
(a && b) != c
更简洁地表达您的意思。
对于多参数,要么/或者没有执行该工作的C ++运算符。例如,如果您编写a ^ b ^ c,则该表达式不是说a,b或c为真的表达式。而是说,a,b和c的奇数为真,可能是其中的1个或全部3个
要表达一般性,或者当a,b和c的类型为bool时,只需编写
(a + b + c) == 1
或使用非bool参数将其转换为bool:
(!!a + !!b + !!c) == 1
使用&=累积布尔结果。
您进一步阐述,
I also need to accumulate Boolean values sometimes, and &= and |=? can be quite useful.
好吧,这相当于分别检查是否满足所有条件或任何条件,并且de Morgans法则告诉您如何从一个条件转到另一个条件。即您只需要其中之一。原则上,您可以将*=用作&&=运算符(因为发现了很好的老乔治·布尔,逻辑AND可以很容易地表示为乘法),但是我认为这会使代码的维护者感到困惑,甚至可能引起误解。
还请考虑:
struct Bool
{
bool value;
void operator&=( bool const v ) { value = value && v; }
operator bool() const { return value; }
};
#include
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的输出:
true
false
结果不同的原因是位级别&=不能将其右手边参数转换为bool。
那么,您希望使用&=这些结果中的哪一个?
如果是前一个true,则最好定义一个运算符(例如上述形式)或命名函数,或者使用右侧表达式的显式转换,或完整编写更新。
按位运算符也具有文本等效项
@Antimony:我最初不理解该注释,但是是的,位级操作具有备用标记bitand,bitor,xor和compl。我认为这就是为什么我使用"可读"的资格。当然,例如compl 42是主观的。 ;-)
据我所知,逻辑运算符可以重载其他参数类型(尽管通常不会重载),所以第一点"保证将参数转换为bool"只是约定的结果,并不保证C ++语言实际上给你。
@MarcvanLeeuwen:可能您的视觉子系统没有注意到"内置的高级布尔运算符!,&&和||"。没错,这些运算符可以重载,然后一个主要问题是语义会微妙地更改,不再需要短路评估。但这就是重载的问题,而不是内置运算符的问题。
是的(我确实很想念)。但是,该语句仍然具有误导性,因为您只是在说那些运算符采用布尔型参数。当(且仅当)选择了运算符的内置版本时,编译器将插入参数的转换。同样,内置的(高级)无符号整数加法运算符+保证将其参数转换为unsigned,但这不会阻止unsigned int n=-1; n+=3.14;实际使用double(或者是float?)加法运算。 (比较您的&=示例。)
@MarcvanLeeuwen:对不起,现在您是看似胡说八道的辩护。但是,您可能没有意识到操作符不能为内置类型重载。关于内置运算符的保证是关于内置运算符的保证。
与Patrick的答案相反,C ++没有用于执行短路异或的^^运算符。如果想一想,使用^^运算符无论如何都没有任何意义:使用异或,结果始终取决于两个操作数。但是,在比较1 & 2和1 && 2时,Patrick关于非bool"布尔"类型的警告同样适用。 Windows GetMessage()函数就是一个典型的例子,该函数返回三态bool:非零,0或-1。
使用&代替&&和|代替||并非罕见的错别字,因此,如果您故意这样做,则应添加注释以说明原因。
不管是否考虑短路,^^运算符仍然有用。它将a)在布尔上下文中评估操作数,并b)保证返回1或0。
@CraigMcQueen。定义inline bool XOR(bool a,bool b) { return a!=b; }并获得所需的内容很简单,只是它是一个函数而不是(中缀)运算符。或者,您可以直接使用!=或重载具有此含义的其他运算符,但是当然,您需要非常小心,以免意外使用相同名称的意外重载。偶然地,||和&&返回true或false,而不是1或0。
似乎!,||和&&在布尔上下文中评估其参数的事实仅是因为这些运算符很少重载以接受其他类型。据我所知,该语言确实允许此类重载,因此不能保证在布尔上下文中评估参数。
帕特里克(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'语句的条件(尽管这似乎很有可能),这是我的假设。但是我对中间布尔值的建议仍然存在。
IIRC,许多C ++编译器会在尝试将按位运算的结果强制转换为布尔值时发出警告。您将必须使用类型转换来使编译器满意。
在if表达式中使用按位运算将引起相同的批评,尽管编译器可能不会这样做。任何非零值都将被视为true,因此" if(7&3)"之类的值将为true。这种行为在Perl中是可以接受的,但是C / C ++是非常明确的语言。我认为Spock的眉毛是尽职调查。 :)我将附加" == 0"或"!= 0"以使其完全清楚您的目标是什么。
但是无论如何,这听起来像是个人喜好。我将通过lint或类似工具运行代码,看看它是否还认为这是不明智的策略。就个人而言,它读起来就像是一个编码错误。
您的帖子激励我进行测试,而gcc并未发出警告! Bummer,因为我将使用该警告来证明让我运行&=来累积布尔结果,因此相信其他人在以后更改我的测试时会看到该警告。我的代码对于偶数整数也将失败,因为它们的取值为0 / false-没有警告!是时候重构...
对布尔使用按位运算有助于节省处理器不必要的分支预测逻辑,这是由于逻辑运算引入了" cmp"指令所致。
用按位运算(所有操作数均为bool)替换逻辑会生成提供相同结果的更有效代码。理想情况下,效率应超过使用逻辑运算进行订购时可以利用的所有短路优势。
尽管程序员应该以注释的理由来注释它,但是这可能会使代码有点不可读。