实用经验 38 a || b和a&&b的陷阱

||和&&是C++中的逻辑或和逻辑与操作符。对于逻辑与操作符当且仅当a和b都为true,其结果才为true。对于逻辑或操作符当且仅当a和b都为false,其结果才为false。

||和&&是C++中特殊的两个操作符。在求值过程中,仅当a不能确定表达式的值时,才会求解b。也就是说仅当下列情形时,必须确保表达式b是可以计算的:

  • 在逻辑表达式中,a的计算结果是true。如果a的计算结果为false,则无论b的值是什么,逻辑表达式的值都为false。当a的值为true时,只有b的值为true时,逻辑与表达式的值才为true。
  • 在逻辑或表达式中,a的计算结果为false。如果a的计算结果为true,则不论b的值是什么,逻辑表达式的值都为true。当a的值为false时,逻辑或表达式的值取决于b的值是否为true。

注解:逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数无法确定该逻辑表达式值时,才会求解其右操作数。我们称这种求值策略为“短路求值”。

||和&&的陷阱包括两个方面:一方面是“短路求值”。另一方面是与“|和&”的混淆。我们首先讨论短路求值的陷阱,之后再进行“|和&”使用混淆讨论。

一般我们都认可“短路求值”会给我们带来益处,我们看短路求值到底给我们带来了什么好处?看下面这段代码。

string s("Expression in C++ is composed......");
string::iterator it = s.begin();
// 将s中第一个单词中的小写字符切换成大写字符
while(it != s.end() && !isspace(*it))
{
	*it = toupper(*it);
	it++;
}

在这段代码中,while循环判断了两个条件,它首先检查it是否已经到达string类型对象的结尾,如果不是,it指向其中的一个字符。只有此检验条件成立时,系统才会计算逻辑&&操作符的右操作数,即保证it确实指向一个真正的字符后,才判断该字符是否为空格。如果遇到空格,或者s中没有空格而已经到达s的尾端时,循环结束。

int item[MAX_ELEMENTS] = {0};

while((i < MAX_ELEMENTS) && item[i] < MAX_VALUE)
{
	// 执行设定操作
}

在C++程序中,这段代码可正常工作得力于&&的“短路求值”。但如果没有“短路求值”,我们看如果整个表达式都被求值,程序会在循环的最后一次迭代中遇到一个错误。我们分析这里面的原因:由于i等于MAX_ELEMENTS,所以表达式item[i]就等价于item[MAX_ELEMENTS],而这是一个数组下标越界操作错误。然而多亏的是C++没有采用这种求值方式,而采用了短路求值方式。避免了这种错误的存在。

我们再看一个利用短路求值的判断的例子,考虑下面这段代码:

if ( (denominator != 0) && ((item/denominator) > MIN_VALUE))
{
	// 执行设定的动作
}

对于这个例子:如果在denominator 等于0 的时候求整个表达式的值,那么位于第二个操作数处的除法就会产生一个除零错误。由于仅当第一部分为真的时候才去求第二个部分的值,因此当denominator 等于0 的时候第二部分就不会参与计算了,因此就不会产生除零错误。

但实际并非仅仅如此。如果“短路求值”我们使用不当,会给我们带来致命的缺陷。我们看下面这个例子。

// 索引自加函数
bool IncIndex(int &iIndex)
{
	iIndex++;
	return true;
}

int main()
{
	int iIndex = -1;
	
	while (iIndex < MAX_INDEX_VALUE && WhileBody(iIndex))
	{
		// 执行和iIndex相关联的操作。
		DoSomething(iIndex);
	}
}

这段代码的期望的运行结果:如果iIndex小于MAX_INDEX_VALUE,执行WhileBody函数和DoSomething函数。否则仅执行WhileBody函数。但实际的结果却不是这样,由于&&的短路求值,导致如果iIndex = -1时,(iIndex <MAX_INDEX_VALUE)为true,WhileBody不会执行,iIndex永远为-1。最终导致while为死循环。这是我们应该引以注意的。

&&、||与&、|的混淆也是我们经常碰到的使用错误,而且这种错误一般很难发现。

逻辑与&&表示的交集运算,只有两个同时成立时结果才为真。逻辑或运算表示的是并集,只要有一个条件成立,结果就为真了。逻辑与和逻辑或都具有短路求值特性。

位与操作(&)需要两个整数操作数,在每个位的位置,如果两个操作数对应的位都为1,则操作结果中该位为1,否则为0。位或操作(|)也需要两个整数操作数,在每个位的位置,如果两个操作数对应的位只要有一个为1,则操作结果中该位为1,否则为0。

通过上面的讲解,我们可以看出:位操作符和逻辑操作符的第一个区别是||和&&操作符具有短路性质,如果表达式的值根据左操作数就可以决定了,它就不再对右操作数进行求值,与之相反,|和&操作符两边的操作数都需要进行求值。

其次逻辑操作符用于测试零值和非零值,而位操作符用于比较他们的操作数中对应的位。看这个例子。

if(a < b && c >d)
if(a < b & c > d)

因为关系操作符产生的或是0或是1,所以这两条语句的结果是一样的。

然而&&和&的差异并非如此简单,我们继续看下面这段代码:

int  i = 1;
int  j = 2;

int main()
{
	printf("i & j = ");
	if (i & j)
	{
		printf("TRUE\n");
	}
	else
	{
		printf("FALSE\n");
	}
 
	printf("i && j =");
	if (i && j)
	{
		printf("TRUE\n");
	}
	else
	{
		printf("FALSE\n");
	}
	
	return 0;
}

你可思考一下,两者的具体差异,这段代码的输出结果。这段代码的输出结果是:

i & j = FALSE
i && j = TRUE

原因是:如果i和j都是非0值,所以i && j语句的值为真,但i & j语句的值却是假,因为i和j的执行的是位模式,没有一个位在两者中的值都为1。

小心地雷:常犯的错误是把位与操作(&)和逻辑与操作(&&)混淆了。同样地,位或操作(|)和逻辑或操作(||)也很容易混淆。在编程过程中使用到&&和||时一定要小心,明确区分此处是使用&&(||)还是使用&(|)。

请谨记

  • 逻辑与&&和逻辑或||都具有短路求值的特性,在使用时需注意了,如你使用正确会给你带来益处,如果你使用不当可能会给你带来致命的陷阱。
  • 明确区分逻辑或(与)和位或(与)的差异。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值