C ++中的逻辑XOR运算符?


292

有这样的事吗?这是我第一次遇到实际需求,但我看不到Stroustrup中列出的需求。我打算写:

// Detect when exactly one of A,B is equal to five.
return (A==5) ^^ (B==5);

但是没有^^运算符。我可以在^这里使用按位运算,并得到正确的答案吗(不管机器是对还是错)。我从不混合&&&,或|||,所以我犹豫着用^^^

我会更愿意编写自己的bool XOR(bool,bool)函数。


57
实际上,吉姆,这不是&和&&之间的唯一区别,例如... 1 && 2是True。但是1&2 =>0。因此,我认为“短路”只是它们碰巧具有的特性。逻辑是更重要的功能...
Brian Postow

6
且不说2 && 3 ==真,但2&3 == 2
大卫·索恩利

1
大卫·汤姆利(David Thomley):是的,但是2 ==>是的,没关系...请记住,确实没有任何布尔值……
布赖恩·波斯托

13
@BrianPostow:实际上,在C ++中有。
AdrianWillenbücher13年

7
正如下面贴出来,这里的丹尼斯里奇的答案,为什么它不存在:c-faq.com/misc/xor.dmr.html
托比亚

Answers:


536

!=运营商服务于这一目的的bool值。


7
但是错误!=错误=>错误
乔纳斯(Jonas)

13
请注意,这仅适用于布尔值。^在那儿可以很好地工作。2!= 1 => 1,这不是您想要的!正如LiraNuna所说,放一个!双方的面前都解决了这个问题。但是同样,您可以使用按位^ ...
Brian Postow

8
是的,我小心地提到“ for boolvalues”,因为它不一定能满足非布尔值的需求。由于这是C ++,因此存在一个实bool类型,而不必为此使用int
Greg Hewgill

29
如果你想这样做的类型a只写!(a) != !(a)
克里斯·鲁茨

6
@ChrisLutz:是的,但是要提防重载的运算符。
rwong 2015年

246

对于真正的逻辑XOR操作,这将起作用:

if(!A != !B) {
    // code here
}

请注意,!这里存在将值转换为布尔值并取反的方法,因此两个不相等的正整数(每个a true)的求和结果为false


6
我不明白为什么A和B被否定!
Martin Hansen

26
主要是将它们转换为布尔值。!!会很好地问问题,但是由于它们需要有所不同,因此否定它们不会造成任何伤害。
LiraNuna

4
问题是,编译器是否能够适当地对此进行优化。
einpoklum 2014年

6
不知道标准化布尔的重要性使我花了2天的时间。
Makan Tayebi

9
@LiraNuna,“为什么A和B取反!” /“主要是将它们转换为布尔值。” -我认为这个答案值得一提。
cp.engr

42

正确的手动逻辑 XOR实现取决于您希望与XOR模仿其他逻辑运算符(||&&)的一般行为的紧密程度。这些运算符有两个重要的方面:1)保证短路评估,2)引入顺序点,3)仅评估操作数一次。

如您所知,XOR评估不能短路,因为结果始终取决于两个操作数。所以1是毫无疑问的。但是2呢?如果您不在乎2,则使用归一化(即bool)值,运算符!=将根据结果执行XOR的工作。!如果需要,可以很容易地用一元对操作数进行标准化。从而!A != !B在这方面实施适当的XOR。

但是,如果您关心额外的序列点,则实现XOR的正确方法都不!=是按位^进行。正确执行XOR(a,b)的一种可能方法如下所示

a ? !b : b

实际上,这与使自制XOR与||和“相似”非常接近&&。当然,仅当您将XOR实现为宏时,这才起作用。函数不会这样做,因为排序将不适用于函数的参数。

也许有人会说,在每个&&和上都有一个序列点的唯一原因||是为了支持短路评估,因此XOR不需要一个。实际上,这是有道理的。但是,值得考虑的是在中间有序列点的XOR。例如,以下表达式

++x > 1 && x < 5

已在C / C ++中定义了行为和特定的结果(至少在排序方面)。因此,可以合理地期望用户定义的逻辑 XOR 具有相同的功能,例如

XOR(++x > 1, x < 5)

!=基于XOR的不具有此属性。


1
您缺少有关||和的其他重要信息&&:C)它们在布尔上下文中评估操作数。也就是说,1 && 2是真实的,不像1 & 2是零。同样,^^运算符对于提供此额外功能(在布尔上下文中评估操作数)可能很有用。例如1 ^^ 2是假的(不同于1 ^ 2)。
Craig McQueen

2
@克雷格·麦昆(Craig McQueen):我没有错过。我的帖子的第二段提到了它。在我看来,将操作数视为布尔值并不是逻辑运算符的关键特征,因为从某种意义上讲,不会仅将它们引入。引入它们的主要原因是短路评估和为此所需的顺序点。
AnT 2011年

如今,您的建议仍然只适用于宏吗?尽管在函数中评估参数的顺序确实是依赖于编译器的,但是当前从左到右的区别难道不是很罕见吗?另外,在此处的注释中可能值得指出的是,如果实现看起来像#define XOR(ll,rr) { ll ? !rr : rr },则调用like int x = 2; XOR(++x > 1, x < 5);将会给出错误的结果。该调用必须具有额外的括号,例如in int x = 2; XOR( (++x > 1), (x < 5) );,以给出正确的预期结果。
RAs

1
由于XOR不能短路,因此不需要序列点。在这方面,XOR更像+。除非您也想主张(++ x)+ x等于2x + 1,否则序列点是不合理的。
hkBst

1
@hkBst:我认为答案的第二部分已完全涵盖了这一点。
AnT

25

还有另一种执行XOR的方法:

bool XOR(bool a, bool b)
{
    return (a + b) % 2;
}

显然可以通过以下方式证明其有效:

#include <iostream>

bool XOR(bool a, bool b)
{
    return (a + b) % 2;
}

int main()
{
    using namespace std;
    cout << "XOR(true, true):\t" << XOR(true, true) << endl
         << "XOR(true, false):\t" << XOR(true, false) << endl
         << "XOR(false, true):\t" << XOR(false, true) << endl
         << "XOR(false, false):\t" << XOR(false, false) << endl
         << "XOR(0, 0):\t\t" << XOR(0, 0) << endl
         << "XOR(1, 0):\t\t" << XOR(1, 0) << endl
         << "XOR(5, 0):\t\t" << XOR(5, 0) << endl
         << "XOR(20, 0):\t\t" << XOR(20, 0) << endl
         << "XOR(6, 6):\t\t" << XOR(5, 5) << endl
         << "XOR(5, 6):\t\t" << XOR(5, 6) << endl
         << "XOR(1, 1):\t\t" << XOR(1, 1) << endl;
    return 0;
}

7
这种方法可以生成一些很慢的代码-使用libstdc ++在clang和gcc上比(!a)!=(!b)慢3-5倍:quick-bench.com/xtv2StFkR8PCkV4fOiqSgeG1T4Q
xaxxon,

18

XOR运算符不能短路;即,您不能仅通过评估左操作数来预测XOR表达式的结果。因此,没有理由提供^^版本。


25
-1是因为&&和&之间的主要区别不仅在于短路。1 && 2为True,但1&2为false。短路只是一个方便的副作用。
布赖恩·波斯托

6
答案是不是在谈论&&&在所有。关键是没有理由引入^^。我怀疑,^^将任何非null值视为1不真正有用的属性。至少我看不到任何用处。
Johannes Schaub-litb

6
还有C ++!=其他语言。如上所示,在C和C ++中,短路不仅是一件好事,而且从根本上来说很重要。:)
Johannes Schaub-litb

13
然后,您应该阅读丹尼斯·里奇(Dennis Ritchie)关于不存在的原因的答案:it.usyd.edu.au/~dasymond/mirror/c-faq/misc/xor.dmr.html
Johannes Schaub-litb

5
这里有丹尼斯里奇的回答,为什么不存在一个工作环节:c-faq.com/misc/xor.dmr.html
托比亚

13

发布了一些不错的代码,比!a!=!b更好地解决了问题。

请注意,我必须添加BOOL_DETAIL_OPEN / CLOSE,这样它才能在MSVC 2010上运行

/* From: http://groups.google.com/group/comp.std.c++/msg/2ff60fa87e8b6aeb

   Proposed code    left-to-right?  sequence point?  bool args?  bool result?  ICE result?  Singular 'b'?
   --------------   --------------  ---------------  ---------- ------------  -----------  -------------
   a ^ b                  no              no             no          no           yes          yes
   a != b                 no              no             no          no           yes          yes
   (!a)!=(!b)             no              no             no          no           yes          yes
   my_xor_func(a,b)       no              no             yes         yes          no           yes
   a ? !b : b             yes             yes            no          no           yes          no
   a ? !b : !!b           yes             yes            no          no           yes          no
   [* see below]          yes             yes            yes         yes          yes          no
   (( a bool_xor b ))     yes             yes            yes         yes          yes          yes

   [* = a ? !static_cast<bool>(b) : static_cast<bool>(b)]

   But what is this funny "(( a bool_xor b ))"? Well, you can create some
   macros that allow you such a strange syntax. Note that the
   double-brackets are part of the syntax and cannot be removed! The set of
   three macros (plus two internal helper macros) also provides bool_and
   and bool_or. That given, what is it good for? We have && and || already,
   why do we need such a stupid syntax? Well, && and || can't guarantee
   that the arguments are converted to bool and that you get a bool result.
     Think "operator overloads". Here's how the macros look like:

   Note: BOOL_DETAIL_OPEN/CLOSE added to make it work on MSVC 2010
  */

#define BOOL_DETAIL_AND_HELPER(x) static_cast<bool>(x):false
#define BOOL_DETAIL_XOR_HELPER(x) !static_cast<bool>(x):static_cast<bool>(x)

#define BOOL_DETAIL_OPEN (
#define BOOL_DETAIL_CLOSE )

#define bool_and BOOL_DETAIL_CLOSE ? BOOL_DETAIL_AND_HELPER BOOL_DETAIL_OPEN
#define bool_or BOOL_DETAIL_CLOSE ? true:static_cast<bool> BOOL_DETAIL_OPEN
#define bool_xor BOOL_DETAIL_CLOSE ? BOOL_DETAIL_XOR_HELPER BOOL_DETAIL_OPEN

6

使用简单:

return ((op1 ? 1 : 0) ^ (op2 ? 1 : 0));

5

我认为这是您在C ++中编写XOR比较的方式:

bool a = true;   // Test by changing to true or false
bool b = false;  // Test by changing to true or false
if (a == !b)     // THIS IS YOUR XOR comparison
{
    // do whatever
}

证明

XOR TABLE
 a   b  XOR
--- --- ---
 T   T   F
 T   F   T
 F   T   T
 F   F   F

a == !b TABLE
 a   b  !b  a == !b
--- --- --- -------
 T   T   F     F
 T   F   T     T
 F   T   F     T
 F   F   T     F

证明是,对输入和输出的详尽研究表明,在两个表中,对于每个输入集,结果始终在两个表中相同。

因此,最初的问题是如何写:

return (A==5) ^^ (B==5)

答案是

return (A==5) == !(B==5);

或者,如果您愿意,写

return !(A==5) == (B==5);

!a!=!b似乎更好,因为它将为您将参数转换为bool。
xaxxon

3

(A || B) && !(A && B)

第一部分是A OR B,这是“包含” OR;第二部分不是A和B。您在一起会得到A或B,但不会同时得到A和B。

这将提供在下面的真值表中证明的XOR。

|-----|-----|-----------|
|  A  |  B  |  A XOR B  |
|-----|-----|-----------|
|  T  |  T  |   False   |
|-----|-----|-----------|
|  T  |  F  |   True    |
|-----|-----|-----------|
|  F  |  T  |   True    |
|-----|-----|-----------|
|  F  |  F  |   False   |
|-----|-----|-----------|

我没有在这种方法上挖掘性能:quick-bench.com/PgNgGN8ATrKt7el1dAaJj7QtuF4
xaxxon

无需对此采取防御措施。
xaxxon

1
@xaxxon:看不到基准测试的重点!?在StringCreation中,您曾用于创建字符串,但在第二个基准测试中却没有。如果在两个基准中放置相同的代码,并对每个基准(XOR和XOR2)调用不同的XOR,则将获得相同的基准结果。那你一直想说什么呢?
SoLaR

1

我使用“ xor”(似乎是一个关键字;在Code :: Blocks中至少它变为粗体),就像您可以使用“ and”代替&&和“ or”代替一样||

if (first xor second)...

是的,这是按位的。抱歉。


2
我猜这些是从某处隐藏的#define。我敢肯定,“ and”和“ xor”不是ansi C中的关键字...(至少不是C79)
Brian Postow

1
@布赖恩Postow:我不知道C79是什么,但在C ++ 98 andxor是标准库的宏。Thay不是来自“某处”,而是来自<iso646.h>。这些宏也位于C99中(不确定C89 / 90)。
AnT

5
@布赖恩Postow:...... xor代表按位异或,虽然,虽然and合乎逻辑的和。
AnT

1
我将C89输入错误的名称为C79 ...,而xor等不在我的K&R副本中。我认为我从来没有使用过iso686.h,至少是在不知情的情况下……所以,是的,它们是某个地方的#defines,您只是偶然知道那个地方B
Brian Postow

2
他们肯定在C99中使用该标头。在C ++中,它们作为“替代标记”集成到语言中,例如,您可以struct A { compl A() { } };定义一个析构函数。
Johannes Schaub-litb

0
#if defined(__OBJC__)
    #define __bool BOOL
    #include <stdbool.h>
    #define __bool bool
#endif

static inline __bool xor(__bool a, __bool b)
{
    return (!a && b) || (a && !b);
}

它按定义工作。有条件的条件是检测您是否使用的是Objective-C,它是要BOOL而不是bool(长度不同!)


3
这违反了双下划线规则。
陶Szelei

@TamásSzelei不一定,因为编译器没有对其进行预处理,因此在Objective-C世界中,双下划线相当普遍。
Maxthon Chan

关于预处理器的好处是,尽管对我而言,它仍然是这种方式的代码味道(为什么仍然要使用宏而不是typedef?)。同样,问题也不是关于Objective-C。
陶Szelei

@TamásSzelei好吧,我以前习惯于跨多种语言共享头文件,通常所有头文件都来自Objective-C。我的新代码现在闻起来并不多,但是仍然不时使用双下划线来遵守ObjC习惯。
Maxthon Chan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.