Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Boolean Logic with Logical Operators
Combine conditions using &&, ||, and ! with short-circuit evaluation.
Logical operators
Relational operators let you test individual conditions, but they can only test one condition at a time. Often, you need to check whether multiple conditions are simultaneously true. For example, entering a secure facility might require both a valid keycard and the correct PIN code—both conditions must be true. Other times, you need to know if any one of several conditions is true. For example, you might get a discount if you're a student, or a senior citizen, or a member—any one of these qualifies.
Logical operators let us test multiple conditions together.
C++ provides three logical operators:
| Operator | Symbol | Example | Operation |
|---|---|---|---|
| Logical NOT | ! | !a | true if a is false, false if a is true |
| Logical AND | && | a && b | true if both a and b are true, false otherwise |
| Logical OR | || | a || b | true if either (or both) a or b are true, false otherwise |
Logical NOT
You've seen the logical NOT unary operator in the Boolean values lesson. Here's a summary of logical NOT:
| Operand | Result |
|---|---|
| true | false |
| false | true |
If logical NOT's operand is true, logical NOT evaluates to false. If the operand is false, logical NOT evaluates to true. Essentially, logical NOT flips a boolean value.
Logical NOT is often used in conditionals:
bool isLocked{temperature > 100}; // isLocked is true if temperature > 100
if (!isLocked)
// access granted
else
// access denied
Be careful with logical NOT's high precedence. New programmers often make this mistake:
#include <iostream>
int main()
{
int speed{50};
int limit{60};
if (!speed > limit)
std::cout << speed << " is not greater than " << limit << '\n';
else
std::cout << speed << " is greater than " << limit << '\n';
return 0;
}
This prints:
50 is greater than 60
But speed isn't greater than limit, so how is this possible? Because logical NOT has higher precedence than the greater-than operator, !speed > limit actually evaluates as (!speed) > limit. Since speed is 50, !speed evaluates to 0, and 0 > limit is false, so the else statement executes.
The correct version:
#include <iostream>
int main()
{
int speed{50};
int limit{60};
if (!(speed > limit))
std::cout << speed << " is not greater than " << limit << '\n';
else
std::cout << speed << " is greater than " << limit << '\n';
return 0;
}
This way, speed > limit evaluates first, then logical NOT flips the boolean result.
When logical NOT is intended to operate on the result of other operators, enclose those operators and their operands in parentheses. Simple uses like `if (!active)` don't need parentheses because precedence isn't an issue.
Logical OR
Logical OR tests whether either of two conditions is true. If the left operand is true, or the right operand is true, or both are true, then logical OR returns true. Otherwise, it returns false.
| Left operand | Right operand | Result |
|---|---|---|
| false | false | false |
| false | true | true |
| true | false | true |
| true | true | true |
Example:
#include <iostream>
int main()
{
std::cout << "Enter a digit: ";
int digit{};
std::cin >> digit;
if (digit == 0 || digit == 1)
std::cout << "You entered 0 or 1\n";
else
std::cout << "You did not enter 0 or 1\n";
return 0;
}
Here, we use logical OR to test whether the left condition (digit == 0) or the right condition (digit == 1) is true. If either (or both) are true, logical OR evaluates to true, and the if-statement executes. If neither is true, logical OR evaluates to false, and the else-statement executes.
New programmers sometimes incorrectly write `if (digit == 0 || 1)`. When `1` evaluates, it implicitly converts to `bool` `true`. Thus this conditional always evaluates to `true`. To compare a variable against multiple values, compare the variable multiple times: `if (digit == 0 || digit == 1)`.
You can chain multiple logical OR statements:
if (digit == 0 || digit == 1 || digit == 2 || digit == 3)
std::cout << "You entered 0, 1, 2, or 3\n";
Don't confuse the logical OR operator (||) with the bitwise OR operator (|) (covered in the Bitwise operators lesson). Though both have OR in the name, they perform different functions. Mixing them up will likely produce incorrect results.
Logical AND
Logical AND tests whether both operands are true. If both operands are true, logical AND returns true. Otherwise, it returns false.
| Left operand | Right operand | Result |
|---|---|---|
| false | false | false |
| false | true | false |
| true | false | false |
| true | true | true |
For example, to check if a variable level is between 5 and 15, we need two conditions: level is greater than 5, and level is less than 15.
#include <iostream>
int main()
{
std::cout << "Enter a level: ";
int level{};
std::cin >> level;
if (level > 5 && level < 15)
std::cout << "Your level is between 5 and 15\n";
else
std::cout << "Your level is not between 5 and 15\n";
return 0;
}
We use logical AND to test whether the left condition (level > 5) AND the right condition (level < 15) are both true. If both are true, logical AND evaluates to true, and the if-statement executes. If neither or only one is true, logical AND evaluates to false, and the else-statement executes.
You can chain multiple logical AND statements:
if (level > 5 && level < 15 && level != 10)
// do something
else
// do something else
If all conditions are true, the if-statement executes. If any condition is false, the else-statement executes.
As with logical and bitwise OR, don't confuse logical AND (&&) with bitwise AND (&).
Short-circuit evaluation
For logical AND to return true, both operands must be true. If the left operand is false, logical AND knows it must return false regardless of the right operand's value. In this case, logical AND returns false immediately without evaluating the right operand. This is called short-circuit evaluation, done primarily for optimization.
Similarly, if the left operand for logical OR is true, the entire OR condition must be true, so the right operand won't be evaluated.
Short-circuit evaluation shows why operators with side effects shouldn't appear in compound expressions. Consider:
if (count == 1 && ++attempts == 2)
// do something
If count doesn't equal 1, the whole condition must be false, so ++attempts never executes! Thus, attempts only increments if count equals 1, which is probably not intended.
Short-circuit evaluation may cause logical OR and logical AND to not evaluate the right operand. Avoid using expressions with side effects in conjunction with these operators.
Logical OR and logical AND are exceptions to the rule that operands may evaluate in any order—the standard explicitly states the left operand must evaluate first.
Advanced note: Only built-in versions of these operators perform short-circuit evaluation. If you overload these operators for custom types, those overloaded operators won't perform short-circuit evaluation.
Mixing ANDs and ORs
Mixing logical AND and logical OR in the same expression is often unavoidable but potentially dangerous.
Because logical AND and logical OR seem like pairs, many programmers assume they have equal precedence (like addition/subtraction or multiplication/division). However, logical AND has higher precedence than logical OR, so logical AND operators evaluate before logical OR operators (unless parenthesized).
New programmers often write expressions like condition1 || condition2 && condition3. Because logical AND has higher precedence, this evaluates as condition1 || (condition2 && condition3), not (condition1 || condition2) && condition3. Hopefully that's what the programmer intended! If assuming left-to-right association (as with addition/subtraction or multiplication/division), the programmer will get unexpected results.
When mixing logical AND and logical OR in the same expression, explicitly parenthesize each operator and its operands. This prevents precedence mistakes, makes code easier to read, and clearly defines intended evaluation. Rather than writing check1 && check2 || check3 && check4, write (check1 && check2) || (check3 && check4).
When mixing logical AND and logical OR in a single expression, explicitly parenthesize each operation to ensure they evaluate as intended.
De Morgan's laws
Many programmers mistakenly think !(a && b) equals !a && !b. Unfortunately, you can't "distribute" logical NOT that way.
De Morgan's laws show how to correctly distribute logical NOT:
!(a && b)is equivalent to!a || !b!(a || b)is equivalent to!a && !b
In other words, when distributing logical NOT, you must flip logical AND to logical OR, and vice-versa.
This can make complex expressions easier to read.
Advanced note: We can prove the first part of De Morgan's Laws by showing !(a && b) equals !a || !b for every possible value of a and b. Using a truth table:
| a | b | !a | !b | !(a && b) | !a || !b |
|---|---|---|---|---|---|
| false | false | true | true | true | true |
| false | true | true | false | true | true |
| true | false | false | true | true | true |
| true | true | false | false | false | false |
The first two columns represent a and b variables. Each row shows one permutation of possible values. Since a and b are boolean, we only need 4 rows to cover every combination.
The remaining columns evaluate expressions based on initial a and b values. Columns three and four calculate !a and !b. Column five calculates !(a && b). Column six calculates !a || !b.
For each row, the fifth column value matches the sixth column value. This means for every possible a and b value, !(a && b) equals !a || !b—which is what we wanted to prove.
We can do the same for the second part:
| a | b | !a | !b | !(a || b) | !a && !b |
|---|---|---|---|---|---|
| false | false | true | true | true | true |
| false | true | true | false | false | false |
| true | false | false | true | false | false |
| true | true | false | false | false | false |
Similarly, for every possible a and b value, !(a || b) equals !a && !b. Thus, they're equivalent.
Where's the logical exclusive or (XOR) operator?
Logical XOR is a logical operator provided in some languages that tests whether an odd number of conditions is true:
| Left operand | Right operand | Result |
|---|---|---|
| false | false | false |
| false | true | true |
| true | false | true |
| true | true | false |
C++ doesn't provide an explicit logical XOR operator (operator^ is bitwise XOR, not logical XOR). Unlike logical OR or logical AND, logical XOR can't be short-circuit evaluated. Because of this, creating a logical XOR operator from logical OR and logical AND operators is challenging.
However, operator!= produces the same result as logical XOR when given bool operands:
| Left operand | Right operand | logical XOR | operator!= |
|---|---|---|---|
| false | false | false | false |
| false | true | true | true |
| true | false | true | true |
| true | true | false | false |
Therefore, logical XOR can be implemented as:
if (a != b) ... // a XOR b, assuming a and b are bool
This extends to multiple operands:
if (a != b != c) ... // a XOR b XOR c, assuming a, b, and c are bool
This evaluates to true if an odd number of operands (a, b, and c) are true.
If operands aren't bool, using operator!= to implement logical XOR won't work as expected.
Advanced note: If you need logical XOR that works with non-Boolean operands, you can static_cast operands to bool:
if (static_cast<bool>(a) != static_cast<bool>(b) != static_cast<bool>(c)) ... // a XOR b XOR c, for any type convertible to bool
However, this is verbose. The following trick is more concise:
if (!!a != !!b != !!c) // a XOR b XOR c, for any type convertible to bool
This uses the fact that operator! (logical NOT) implicitly converts its operand to bool. However, operator! also inverts the bool from true to false or vice-versa. Therefore, we apply operator! twice. The first time does the implicit conversion and inverts. The second time inverts back to the original value. This double-inversion is necessary when a multiple-operand XOR has an odd number of operands; otherwise, the XOR produces an inverted result.
Neither approach is very intuitive, so document them well if you use them.
Alternative operator representations
Many C++ operators (like ||) have names that are just symbols. Historically, not all keyboards and language standards supported all symbols needed to type these operators. As such, C++ supports alternative keywords for operators that use words instead of symbols. For example, instead of ||, you can use the keyword or.
The full list is here. Of particular note:
| Operator name | Keyword alternate |
|---|---|
| && | and |
| || | or |
| ! | not |
This means the following are identical:
std::cout << !active && (ready || prepared);
std::cout << not active and (ready or prepared);
While these alternatives might seem easier to understand now, most experienced C++ developers prefer symbolic names over keyword names. We recommend learning and using symbolic names, as this is what you'll commonly find in existing code.
Summary
- Logical operators:
!(NOT),&&(AND),||(OR); test multiple conditions together - Logical NOT (
!): Unary operator that flips boolean values (true becomes false, false becomes true) - NOT precedence: Logical NOT has high precedence; parenthesize complex expressions like
!(speed > limit)instead of!speed > limit - Logical OR (
||): Returns true if either (or both) operands are true - Common OR mistake: Writing
if (digit == 0 || 1)instead ofif (digit == 0 || digit == 1)—the first always evaluates to true - Logical AND (
&&): Returns true only when both operands are true - Short-circuit evaluation: Logical operators stop evaluating as soon as the result is known (AND stops if left is false; OR stops if left is true)
- Side effects warning: Avoid expressions with side effects in logical operators (e.g.,
count == 1 && ++attempts == 2)—the right operand may not execute - Evaluation order guarantee: Unlike most operators, logical AND and OR always evaluate left operand first
- Precedence of AND vs OR: Logical AND (
&&) has higher precedence than logical OR (||); always parenthesize mixed expressions for clarity - De Morgan's laws:
!(a && b)equals!a || !b;!(a || b)equals!a && !b—when distributing NOT, flip AND to OR and vice-versa - Logical XOR: C++ has no logical XOR operator; use
!=for boolean operands (e.g.,a != bfor bool XOR) - Alternative keywords:
andfor&&,orfor||,notfor!; symbolic names are preferred in practice
Logical operators are essential for complex conditionals but require care with precedence, short-circuit evaluation, and side effects. Always parenthesize mixed AND/OR expressions to make intent clear and prevent precedence errors. Avoid using expressions with side effects in logical operators, as short-circuit evaluation may prevent their execution.
Boolean Logic with Logical Operators - Quiz
Test your understanding of the lesson.
Practice Exercises
Logical Operators
Practice using logical operators for combining conditions.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!