1.BUG记录
最近在写一道算法题的时候,遇到了一个bug,主要是由于逻辑运算符的执行方式所导致的。
代码如下:
bool check(int num, vector<int>& locked_num) {
int flag = false;
// 判断当前节点是否被上锁
if (locks.find(num) != locks.end() && locks[num] != -1) {
locked_num.push_back(num);
flag = true;
}
for (const auto& next : children[num]) {
// 出bug的地方
flag = flag || check(next, locked_num);
}
return flag;
}
代码的主要作用是遍历多叉树,返回数中是否有被上锁的节点,同时将所有节点存储到指定的容器中。
可以看到,这是一个递归函数,使用for循环,对当前节点的所有子树进行遍历,问题就出在了,下面代码所示的这一句上面。
flag = flag || check(next, locked_num);
这里就不得不提逻辑或运算符的执行方式了。
对于 a || b 这个表达式,如果a已经为true,那么程序将不会对b进行判断,直接忽略掉b的部分。
所以,对于我上述的代码来说,一旦在某次循环中flag置为true了,或者如上述代码中,第一个if已经将flag置为true了,那么后续的递归是不会进行下去的。
所以修改此bug只需要将递归函数提前或者使用|=,具体如下:
flag = check(next, locked_num) || flag;
// 或者
flag |= check(next, locked_num);
2.C++的逻辑运算符
这里只介绍逻辑或与逻辑与运算符。
对于逻辑或运算符来说,a || b ,当a为true时,不会执行b。
对于逻辑与运算符来说,a && b,当a为false时,不会执行b。
因为当上述两种情况出现时,无论b的结果是什么,都不会影响整个表达式的结果。
为什么会出现这种情况?
因为C++规定了,逻辑或和逻辑与运算符是顺序执行的(在C++11之前,也被称为顺序点——sequence point)。简单来说,就是运算符左边的表达式优于右边的表达式。
这种执行方式有助于我们规避一些错误,举个最简单的例子:
if (x != 0 && 10 / x > 2) {...}
上述的例子中,能够帮助程序规避掉0除的问题。
同时,我们也可以进行区间判断(借用C++ primer plus的例子):
if (age > 17 && age < 35) {
...
} else if (age >= 35 && age < 50) {
...
} else if (age >= 50 && age < 65) {
...
} else {
...
}
还有许多其他的应用,这里就不继续介绍了。
总之,我们能够灵活地使用逻辑运算符的这种性质来达到我们的目的。
3.讨论
最后,感叹一下,某些知识还是需要一段印象深刻的经历才能牢记啊。在此之前,这种逻辑运算符有这种特点我也十分清楚,但是写代码的时候就是没有注意,导致短短的一行代码,我debug了好久才找到问题。还是需要不断地实践,在充满错误的道路上不断前行!!!与看到这的读者共勉。