Relational & Logical Operators
Master comparison and logical operators for decision-making in C. Learn to write conditions, combine boolean expressions, and understand operator precedence in complex logical statements.
Relational Operators
Relational operators compare two values and return a boolean result - in C, this means 1 for true and 0 for false. These operators form the foundation of decision-making in programs, enabling if statements, while loops, and conditional expressions. Understanding relational operators is essential for controlling program flow based on data comparisons.
C doesn't have a dedicated boolean type in C89 (though C99 added _Bool and stdbool.h). Instead, any non-zero value is considered true, and zero is false. Relational operators always return 1 (true) or 0 (false), but when testing conditions, any integer value works - this flexibility is powerful but requires care to avoid bugs.
#include <stdio.h>
int main(void) {
int a = 10, b = 20;
/* Equal to */
printf("%d == %d is %d\n", a, b, a == b); // 0 (false)
printf("%d == %d is %d\n", a, a, a == a); // 1 (true)
/* Not equal to */
printf("%d != %d is %d\n", a, b, a != b); // 1 (true)
printf("%d != %d is %d\n", a, a, a != a); // 0 (false)
/* Greater than */
printf("%d > %d is %d\n", b, a, b > a); // 1 (true)
printf("%d > %d is %d\n", a, b, a > b); // 0 (false)
/* Less than */
printf("%d < %d is %d\n", a, b, a < b); // 1 (true)
printf("%d < %d is %d\n", b, a, b < a); // 0 (false)
/* Greater than or equal to */
printf("%d >= %d is %d\n", a, a, a >= a); // 1 (true)
printf("%d >= %d is %d\n", b, a, b >= a); // 1 (true)
printf("%d >= %d is %d\n", a, b, a >= b); // 0 (false)
/* Less than or equal to */
printf("%d <= %d is %d\n", a, b, a <= b); // 1 (true)
printf("%d <= %d is %d\n", a, a, a <= a); // 1 (true)
printf("%d <= %d is %d\n", b, a, b <= a); // 0 (false)
return 0;
}The Six Relational Operators:
- == Equal to
- != Not equal to
- > Greater than
- < Less than
- >= Greater than or equal to
- <= Less than or equal to
Using Relational Operators in Conditions
Relational operators shine in control flow statements. They determine which code paths execute based on runtime values. The ability to compare values and branch accordingly is what makes programs interactive and responsive to different inputs and situations.
/* if statements */
int age = 18;
if (age >= 18) {
printf("You are an adult\n");
}
if (age < 18) {
printf("You are a minor\n");
}
/* if-else */
int score = 85;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else {
printf("Grade: F\n");
}
/* while loops */
int count = 0;
while (count < 5) {
printf("%d ", count);
count++;
}
printf("\n"); // Output: 0 1 2 3 4
/* for loops */
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");
/* Common mistake: = vs == */
int x = 5;
if (x = 10) { // OOPS: Assignment, not comparison!
printf("This always runs!\n"); // x assigned 10, which is true
}
/* Correct comparison */
if (x == 10) { // Comparison
printf("x equals 10\n");
}Logical Operators
Logical operators combine multiple boolean expressions into complex conditions. They allow you to check multiple conditions simultaneously, creating sophisticated decision logic. The three logical operators are AND (&&), OR (||), and NOT (!). These operators use short-circuit evaluation - they stop evaluating as soon as the result is determined.
Logical AND (&&)
The AND operator returns true only if both operands are true. It short-circuits: if the first operand is false, the second isn't evaluated because the result must be false regardless. This behavior can prevent errors when the second condition depends on the first being true.
/* Logical AND - both must be true */
int age = 25;
int has_license = 1;
if (age >= 18 && has_license) {
printf("You can drive\n");
}
/* Truth table for AND */
printf("0 && 0 = %d\n", 0 && 0); // 0 (false)
printf("0 && 1 = %d\n", 0 && 1); // 0 (false)
printf("1 && 0 = %d\n", 1 && 0); // 0 (false)
printf("1 && 1 = %d\n", 1 && 1); // 1 (true)
/* Short-circuit evaluation */
int x = 0;
int y = 10;
if (x != 0 && y / x > 2) { // Safe: second part not evaluated when x==0
printf("This won't crash\n");
}
/* Multiple conditions */
int temp = 72;
int humidity = 50;
int pressure = 1013;
if (temp > 65 && temp < 85 && humidity < 70 && pressure > 1000) {
printf("Perfect weather conditions\n");
}Logical OR (||)
The OR operator returns true if at least one operand is true. It also short-circuits: if the first operand is true, the second isn't evaluated because the result is already determined to be true. This makes OR efficient and allows for safe conditional checks.
/* Logical OR - at least one must be true */
char grade = 'A';
if (grade == 'A' || grade == 'B') {
printf("Good grade!\n");
}
/* Truth table for OR */
printf("0 || 0 = %d\n", 0 || 0); // 0 (false)
printf("0 || 1 = %d\n", 0 || 1); // 1 (true)
printf("1 || 0 = %d\n", 1 || 0); // 1 (true)
printf("1 || 1 = %d\n", 1 || 1); // 1 (true)
/* Short-circuit evaluation */
int found = 1;
int search_result = 0;
if (found || (search_result = expensive_search()) != 0) {
printf("Found!\n"); // expensive_search() not called if found==1
}
/* Multiple conditions */
char day = 'S';
if (day == 'S' || day == 'U') { // Saturday or Sunday
printf("It's the weekend!\n");
}Logical NOT (!)
The NOT operator inverts a boolean value - true becomes false, false becomes true. It's a unary operator (works on one operand) and is useful for negating conditions or checking for false values. In C, !0 is 1 (true), and !(anything non-zero) is 0 (false).
/* Logical NOT - inverts boolean value */
int is_raining = 0;
if (!is_raining) {
printf("No umbrella needed\n");
}
/* Truth table for NOT */
printf("!0 = %d\n", !0); // 1 (true)
printf("!1 = %d\n", !1); // 0 (false)
printf("!5 = %d\n", !5); // 0 (false, any non-zero is true)
printf("!-3 = %d\n", !-3); // 0 (false, negative numbers are true)
/* Double negation */
printf("!!0 = %d\n", !!0); // 0 (false)
printf("!!5 = %d\n", !!5); // 1 (true, converts non-zero to 1)
/* Negating comparisons */
int x = 10;
if (!(x > 20)) { // Same as: x <= 20
printf("x is not greater than 20\n");
}
/* Clearer to write directly */
if (x <= 20) {
printf("x is 20 or less\n");
}
/* NOT with other operators */
int is_valid = 1;
if (!is_valid) {
printf("Invalid input\n");
} else {
printf("Valid input\n");
}Combining Logical Operators
Complex conditions often require combining multiple logical operators. Understanding precedence and using parentheses for clarity prevents logic errors. The precedence is: ! (highest), &&, || (lowest). However, explicit parentheses make code much more readable and less error-prone.
/* Complex conditions */
int age = 25;
int income = 50000;
int credit_score = 700;
/* Loan approval logic */
if ((age >= 18 && age <= 65) && (income >= 30000 || credit_score >= 650)) {
printf("Loan approved\n");
} else {
printf("Loan denied\n");
}
/* De Morgan's Laws */
int a = 1, b = 0;
/* NOT (A AND B) = (NOT A) OR (NOT B) */
printf("!(a && b) = %d\n", !(a && b)); // 1
printf("(!a || !b) = %d\n", (!a || !b)); // 1 (same result)
/* NOT (A OR B) = (NOT A) AND (NOT B) */
printf("!(a || b) = %d\n", !(a || b)); // 0
printf("(!a && !b) = %d\n", (!a && !b)); // 0 (same result)
/* Range checking */
int temperature = 72;
if (temperature >= 65 && temperature <= 75) {
printf("Comfortable temperature\n");
}
/* Exclusive OR (XOR) - true if exactly one is true */
int x = 1, y = 0;
if ((x && !y) || (!x && y)) { // Manual XOR
printf("Exactly one is true\n");
}
/* Using bitwise XOR for boolean (works but less clear) */
if (!!(x ^ y)) { // Convert bitwise result to boolean
printf("Exactly one is true\n");
}
/* Multiple conditions with precedence */
int has_ticket = 1;
int has_id = 1;
int age_check = 1;
int banned = 0;
// Precedence: ! then && then ||
if (has_ticket && has_id && (age_check || !banned)) {
printf("Entry allowed\n");
}
/* More readable with explicit parentheses */
if (has_ticket && has_id && (age_check || (!banned))) {
printf("Entry allowed\n");
}Short-Circuit Evaluation
Short-circuit evaluation is an important optimization and safety feature. With &&, if the first operand is false, the second isn't evaluated. With ||, if the first operand is true, the second isn't evaluated. This behavior can prevent errors and improve performance, but relying on side effects in the second operand can lead to subtle bugs.
/* Safe division using short-circuit */
int numerator = 10;
int denominator = 0;
if (denominator != 0 && numerator / denominator > 2) {
printf("Quotient is large\n");
}
// Second condition not evaluated when denominator==0 (safe!)
/* Pointer safety check */
int *ptr = NULL;
if (ptr != NULL && *ptr > 0) {
printf("Positive value\n");
}
// *ptr not dereferenced when ptr is NULL (safe!)
/* Array bounds checking */
int arr[] = {1, 2, 3, 4, 5};
int size = 5;
int index = 10;
if (index >= 0 && index < size && arr[index] > 0) {
printf("Valid positive element\n");
}
// arr[index] not accessed if index out of bounds (safe!)
/* Performance optimization with short-circuit */
int expensive_check(void) {
printf("Expensive operation running...\n");
return 1;
}
int quick_result = 0;
if (quick_result || expensive_check()) {
printf("Condition met\n");
}
// expensive_check() not called if quick_result is true
/* Side effect gotcha */
int counter = 0;
int condition1 = 1;
if (condition1 || (counter++ > 0)) {
printf("Condition met\n");
}
printf("Counter: %d\n", counter); // Still 0! counter++ not executed
/* Don't rely on side effects */
// Bad:
if (condition1 || (counter++ > 0)) {"{ }"}
/* Good: Separate the side effect */
counter++;
if (condition1 || (counter > 0)) {"{ }"}Common Pitfalls and Best Practices
Relational and logical operators are straightforward but have subtle gotchas that catch even experienced programmers. Being aware of these issues helps you write correct, maintainable code.
/* Pitfall 1: Assignment vs Comparison */
int x = 5;
// Wrong:
if (x = 10) { // Assignment! Always true (10 is non-zero)
printf("Always runs\n");
}
// Correct:
if (x == 10) { // Comparison
printf("x equals 10\n");
}
/* Pitfall 2: Comparing floating-point numbers */
double a = 0.1 + 0.2;
double b = 0.3;
if (a == b) { // May be false due to rounding!
printf("Equal\n");
}
// Better: Use epsilon comparison
#include <math.h>
#define EPSILON 0.00001
if (fabs(a - b) < EPSILON) {
printf("Approximately equal\n");
}
/* Pitfall 3: Chaining comparisons */
int age = 25;
// Wrong (doesn't work like math notation):
if (18 <= age <= 65) { // Parsed as: (18 <= age) <= 65
// (18 <= 25) is 1 (true), then (1 <= 65) is true
// This is ALWAYS true!
}
// Correct:
if (age >= 18 && age <= 65) {
printf("Valid age range\n");
}
/* Pitfall 4: Operator precedence confusion */
int a = 1, b = 0, c = 1;
// Ambiguous:
if (a || b && c) { // What executes?
// Answer: && has higher precedence than ||
// Evaluated as: a || (b && c)
}
// Clear:
if (a || (b && c)) {
printf("Clear intention\n");
}
/* Best Practices */
// 1. Use parentheses for clarity
if ((x > 0) && (y > 0)) { // Very clear
printf("Both positive\n");
}
// 2. Keep conditions simple
// Bad:
if ((a > 5 && b < 10) || (c == 0 && d != 5) || (e >= 20 && f <= 30)) {"{ }"}
// Good: Break into named variables
int condition1 = (a > 5 && b < 10);
int condition2 = (c == 0 && d != 5);
int condition3 = (e >= 20 && f <= 30);
if (condition1 || condition2 || condition3) {"{ }"}
// 3. Put constants on left (Yoda conditions) to catch mistakes
if (10 == x) { // If you write = instead of ==, compiler error!
printf("x is 10\n");
}
// if (10 = x) would be a compile error
// 4. Avoid complex negations
// Bad:
if (!(x > 10 && y < 5)) {"{ }"}
// Better (De Morgan's):
if (x <= 10 || y >= 5) {"{ }"}Summary & What's Next
Key Takeaways:
- ✅ Relational: ==, !=, >, <, >=, <=
- ✅ Logical: && (AND), || (OR), ! (NOT)
- ✅ Relational operators return 1 (true) or 0 (false)
- ✅ In C, any non-zero value is true, zero is false
- ✅ Short-circuit evaluation stops early when result determined
- ✅ Watch out for = vs == mistake
- ✅ Use parentheses for clarity in complex conditions
- ✅ Don't chain comparisons like math notation