Operators and Expressions
Introduction
Operators are symbols that perform specific operations on operands (variables, constants, or expressions). Expressions are combinations of operators and operands that evaluate to a single value. Understanding operators and how to construct expressions is fundamental to C programming, as they form the building blocks of all program logic.
This chapter will cover all major categories of operators in C, their precedence and associativity rules, and how to write effective expressions.
Categories of Operators
C provides a rich set of operators that can be categorized as follows:
- Arithmetic Operators: Perform mathematical operations
- Relational Operators: Compare values
- Logical Operators: Combine boolean expressions
- Bitwise Operators: Manipulate individual bits
- Assignment Operators: Assign values to variables
- Increment/Decrement Operators: Modify values by one
- Conditional Operator: Ternary selection operator
- Special Operators: sizeof, address-of, dereference, etc.
Arithmetic Operators
Arithmetic operators perform basic mathematical operations:
| Operator | Description | Example | Result |
|---|---|---|---|
+ |
Addition | 5 + 3 |
8 |
- |
Subtraction | 5 - 3 |
2 |
* |
Multiplication | 5 * 3 |
15 |
/ |
Division | 5 / 3 |
1 (integer division) |
% |
Modulus (remainder) | 5 % 3 |
2 |
Basic Arithmetic Operations
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", a + b);
printf("a - b = %d\n", a - b);
printf("a * b = %d\n", a * b);
printf("a / b = %d\n", a / b); // Integer division
printf("a %% b = %d\n", a % b); // Modulus
// Floating-point arithmetic
float x = 10.0f, y = 3.0f;
printf("\nFloat arithmetic:\n");
printf("x = %.1f, y = %.1f\n", x, y);
printf("x / y = %.2f\n", x / y); // Floating-point division
return 0;
}Division and Modulus Behavior
#include <stdio.h>
int main() {
// Integer division truncates toward zero
printf("Integer division:\n");
printf("7 / 3 = %d\n", 7 / 3); // 2
printf("-7 / 3 = %d\n", -7 / 3); // -2
printf("7 / -3 = %d\n", 7 / -3); // -2
printf("-7 / -3 = %d\n", -7 / -3); // 2
// Modulus result has the same sign as the dividend
printf("\nModulus operations:\n");
printf("7 %% 3 = %d\n", 7 % 3); // 1
printf("-7 %% 3 = %d\n", -7 % 3); // -1
printf("7 %% -3 = %d\n", 7 % -3); // 1
printf("-7 %% -3 = %d\n", -7 % -3); // -1
return 0;
}Relational Operators
Relational operators compare two values and return a boolean result (1 for true, 0 for false):
| Operator | Description | Example | Result |
|---|---|---|---|
== |
Equal to | 5 == 3 |
0 (false) |
!= |
Not equal to | 5 != 3 |
1 (true) |
> |
Greater than | 5 > 3 |
1 (true) |
< |
Less than | 5 < 3 |
0 (false) |
>= |
Greater than or equal to | 5 >= 5 |
1 (true) |
<= |
Less than or equal to | 5 <= 3 |
0 (false) |
Relational Operations
#include <stdio.h>
int main() {
int x = 10, y = 20;
printf("x = %d, y = %d\n", x, y);
printf("x == y: %d\n", x == y);
printf("x != y: %d\n", x != y);
printf("x > y: %d\n", x > y);
printf("x < y: %d\n", x < y);
printf("x >= y: %d\n", x >= y);
printf("x <= y: %d\n", x <= y);
// Floating-point comparison (be careful!)
float a = 0.1f + 0.2f;
float b = 0.3f;
printf("\nFloating-point comparison:\n");
printf("a = %.20f\n", a);
printf("b = %.20f\n", b);
printf("a == b: %d\n", a == b);
return 0;
}Logical Operators
Logical operators work with boolean values and are used to combine or invert conditions:
| Operator | Description | Example | Result |
|---|---|---|---|
&& |
Logical AND | 1 && 0 |
0 (false) |
|| |
Logical OR | 1 || 0 |
1 (true) |
! |
Logical NOT | !1 |
0 (false) |
Logical Operations
#include <stdio.h>
int main() {
int a = 1, b = 0, c = 1;
printf("a = %d, b = %d, c = %d\n", a, b, c);
printf("a && b: %d\n", a && b); // 1 AND 0 = 0
printf("a || b: %d\n", a || b); // 1 OR 0 = 1
printf("!a: %d\n", !a); // NOT 1 = 0
printf("a && c: %d\n", a && c); // 1 AND 1 = 1
printf("a || (b && c): %d\n", a || (b && c)); // 1 OR (0 AND 1) = 1
// Short-circuit evaluation
printf("\nShort-circuit evaluation:\n");
int x = 5, y = 0;
if (y != 0 && x / y > 1) { // Second condition not evaluated
printf("This won't print\n");
} else {
printf("Short-circuit prevented division by zero\n");
}
return 0;
}Bitwise Operators
Bitwise operators work at the bit level and are particularly useful in systems programming and embedded development:
| Operator | Description | Example | Result |
|---|---|---|---|
& |
Bitwise AND | 5 & 3 |
1 |
| |
Bitwise OR | 5 | 3 |
7 |
^ |
Bitwise XOR | 5 ^ 3 |
6 |
~ |
Bitwise NOT | ~5 |
-6 |
<< |
Left shift | 5 << 1 |
10 |
>> |
Right shift | 5 >> 1 |
2 |
Bitwise Operations
#include <stdio.h>
void print_binary(unsigned int num) {
for (int i = 31; i >= 0; i--) {
printf("%d", (num >> i) & 1);
if (i % 4 == 0) printf(" ");
}
printf("\n");
}
int main() {
unsigned int a = 12; // Binary: 1100
unsigned int b = 10; // Binary: 1010
printf("a = %u, b = %u\n", a, b);
printf("a in binary: ");
print_binary(a);
printf("b in binary: ");
print_binary(b);
printf("\nBitwise operations:\n");
printf("a & b = %u (AND)\n", a & b);
printf("a | b = %u (OR)\n", a | b);
printf("a ^ b = %u (XOR)\n", a ^ b);
printf("~a = %u (NOT)\n", ~a);
printf("a << 1 = %u (Left shift)\n", a << 1);
printf("a >> 1 = %u (Right shift)\n", a >> 1);
// Practical example: Setting, clearing, and toggling bits
unsigned char flags = 0b00001010; // 10 in decimal
printf("\nFlag operations:\n");
printf("Original flags: ");
print_binary(flags);
// Set bit 2 (make it 1)
flags |= (1 << 2);
printf("After setting bit 2: ");
print_binary(flags);
// Clear bit 3 (make it 0)
flags &= ~(1 << 3);
printf("After clearing bit 3: ");
print_binary(flags);
// Toggle bit 1
flags ^= (1 << 1);
printf("After toggling bit 1: ");
print_binary(flags);
return 0;
}Assignment Operators
Assignment operators assign values to variables. C provides several compound assignment operators for convenience:
| Operator | Description | Equivalent To |
|---|---|---|
= |
Simple assignment | a = b |
+= |
Add and assign | a = a + b |
-= |
Subtract and assign | a = a - b |
*= |
Multiply and assign | a = a * b |
/= |
Divide and assign | a = a / b |
%= |
Modulus and assign | a = a % b |
&= |
Bitwise AND and assign | a = a & b |
|= |
Bitwise OR and assign | a = a | b |
^= |
Bitwise XOR and assign | a = a ^ b |
<<= |
Left shift and assign | a = a << b |
>>= |
Right shift and assign | a = a >> b |
Assignment Operations
#include <stdio.h>
int main() {
int a = 10;
printf("Initial value: a = %d\n", a);
a += 5; // a = a + 5
printf("After a += 5: a = %d\n", a);
a -= 3; // a = a - 3
printf("After a -= 3: a = %d\n", a);
a *= 2; // a = a * 2
printf("After a *= 2: a = %d\n", a);
a /= 4; // a = a / 4
printf("After a /= 4: a = %d\n", a);
a %= 3; // a = a % 3
printf("After a %%= 3: a = %d\n", a);
// Multiple assignments
int x, y, z;
x = y = z = 5; // All variables get value 5
printf("\nMultiple assignment: x = %d, y = %d, z = %d\n", x, y, z);
return 0;
}Increment and Decrement Operators
These operators increase or decrease a variable’s value by 1:
| Operator | Description | Example | Result |
|---|---|---|---|
++ |
Pre-increment | ++a |
Increment a, then use new value |
++ |
Post-increment | a++ |
Use current value, then increment a |
-- |
Pre-decrement | --a |
Decrement a, then use new value |
-- |
Post-decrement | a-- |
Use current value, then decrement a |
Increment/Decrement Examples
#include <stdio.h>
int main() {
int a = 5, b = 5;
int result1, result2;
printf("Initial: a = %d, b = %d\n", a, b);
// Pre-increment
result1 = ++a;
printf("After ++a: a = %d, result1 = %d\n", a, result1);
// Post-increment
result2 = b++;
printf("After b++: b = %d, result2 = %d\n", b, result2);
// Practical example in loops
printf("\nLoop examples:\n");
// Using pre-increment
printf("Pre-increment loop:\n");
for (int i = 0; i < 3; ++i) {
printf("i = %d\n", i);
}
// Using post-increment
printf("Post-increment loop:\n");
for (int i = 0; i < 3; i++) {
printf("i = %d\n", i);
}
// Be careful with complex expressions
int x = 10;
int y = ++x + x++; // Undefined behavior!
printf("\nUndefined behavior example: y = %d\n", y);
return 0;
}Conditional (Ternary) Operator
The conditional operator is C’s only ternary operator, taking three operands:
condition ? value_if_true : value_if_falseConditional Operator Examples
#include <stdio.h>
int main() {
int a = 10, b = 20;
// Simple conditional assignment
int max = (a > b) ? a : b;
printf("Maximum of %d and %d is %d\n", a, b, max);
// Conditional string selection
char *message = (a % 2 == 0) ? "even" : "odd";
printf("%d is %s\n", a, message);
// Nested conditional operators
int score = 85;
char *grade = (score >= 90) ? "A" :
(score >= 80) ? "B" :
(score >= 70) ? "C" :
(score >= 60) ? "D" : "F";
printf("Score %d gets grade %s\n", score, grade);
// Conditional with function calls (be careful with side effects)
int x = 5, y = 10;
int result = (x > y) ? (x += 5) : (y += 5);
printf("After conditional: x = %d, y = %d, result = %d\n", x, y, result);
return 0;
}Special Operators
sizeof Operator
Returns the size (in bytes) of a data type or variable:
#include <stdio.h>
int main() {
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of double: %zu bytes\n", sizeof(double));
printf("Size of pointer: %zu bytes\n", sizeof(int*));
int array[10];
printf("Size of array[10]: %zu bytes\n", sizeof(array));
printf("Size of array element: %zu bytes\n", sizeof(array[0]));
return 0;
}Address-of (&) and Dereference (*) Operators
Used with pointers (covered in detail in Module 6):
#include <stdio.h>
int main() {
int number = 42;
int *ptr = &number; // Address-of operator
printf("Value of number: %d\n", number);
printf("Address of number: %p\n", (void*)&number);
printf("Value of ptr: %p\n", (void*)ptr);
printf("Value pointed to by ptr: %d\n", *ptr); // Dereference operator
return 0;
}Operator Precedence and Associativity
Operators in C have specific precedence levels and associativity rules that determine the order of evaluation in expressions:
Precedence Levels (Highest to Lowest)
- Postfix:
(),[],->,.,++,--(Left to Right) - Unary:
++,--,+,-,!,~,(type),*,&,sizeof(Right to Left) - Multiplicative:
*,/,%(Left to Right) - Additive:
+,-(Left to Right) - Shift:
<<,>>(Left to Right) - Relational:
<,<=,>,>=(Left to Right) - Equality:
==,!=(Left to Right) - Bitwise AND:
&(Left to Right) - Bitwise XOR:
^(Left to Right) - Bitwise OR:
|(Left to Right) - Logical AND:
&&(Left to Right) - Logical OR:
||(Left to Right) - Conditional:
?:(Right to Left) - Assignment:
=,+=,-=, etc. (Right to Left) - Comma:
,(Left to Right)
Precedence Examples
#include <stdio.h>
int main() {
int a = 5, b = 3, c = 2;
// Multiplication has higher precedence than addition
int result1 = a + b * c; // 5 + (3 * 2) = 11
int result2 = (a + b) * c; // (5 + 3) * 2 = 16
printf("a + b * c = %d\n", result1);
printf("(a + b) * c = %d\n", result2);
// Relational operators have lower precedence than arithmetic
int result3 = a > b + c; // a > (b + c) = 5 > 5 = 0
int result4 = (a > b) + c; // (a > b) + c = 1 + 2 = 3
printf("a > b + c = %d\n", result3);
printf("(a > b) + c = %d\n", result4);
// Logical operators have lower precedence than relational
int x = 10, y = 5, z = 15;
int result5 = x > y && y < z; // (x > y) && (y < z) = 1 && 1 = 1
int result6 = x > y & y < z; // x > (y & y) < z = 10 > 1 < 15 = 1
printf("x > y && y < z = %d\n", result5);
printf("x > y & y < z = %d\n", result6);
return 0;
}Practical Examples
Mathematical Expression Evaluator
#include <stdio.h>
int main() {
double x, y, z;
printf("Enter three numbers: ");
scanf("%lf %lf %lf", &x, &y, &z);
// Complex mathematical expression
double result = (x + y) * z / (x - y) + (x * y - z);
printf("Result of (x + y) * z / (x - y) + (x * y - z):\n");
printf("= (%.2f + %.2f) * %.2f / (%.2f - %.2f) + (%.2f * %.2f - %.2f)\n",
x, y, z, x, y, x, y, z);
printf("= %.2f\n", result);
return 0;
}Bit Manipulation Utility
#include <stdio.h>
// Function to check if a bit is set
int is_bit_set(unsigned int num, int position) {
return (num >> position) & 1;
}
// Function to set a bit
unsigned int set_bit(unsigned int num, int position) {
return num | (1 << position);
}
// Function to clear a bit
unsigned int clear_bit(unsigned int num, int position) {
return num & ~(1 << position);
}
// Function to toggle a bit
unsigned int toggle_bit(unsigned int num, int position) {
return num ^ (1 << position);
}
void print_bits(unsigned int num) {
printf("Binary: ");
for (int i = 31; i >= 0; i--) {
printf("%d", (num >> i) & 1);
if (i % 4 == 0) printf(" ");
}
printf("\n");
}
int main() {
unsigned int number = 0b10101010;
printf("Original number: %u\n", number);
print_bits(number);
// Check bit at position 3
printf("Bit at position 3: %d\n", is_bit_set(number, 3));
// Set bit at position 2
number = set_bit(number, 2);
printf("After setting bit 2: %u\n", number);
print_bits(number);
// Clear bit at position 7
number = clear_bit(number, 7);
printf("After clearing bit 7: %u\n", number);
print_bits(number);
// Toggle bit at position 5
number = toggle_bit(number, 5);
printf("After toggling bit 5: %u\n", number);
print_bits(number);
return 0;
}Conditional Logic Demonstrator
#include <stdio.h>
int main() {
int score;
printf("Enter student score (0-100): ");
scanf("%d", &score);
// Validate input
if (score < 0 || score > 100) {
printf("Invalid score!\n");
return 1;
}
// Determine letter grade using conditional operators
char *letter_grade = (score >= 90) ? "A" :
(score >= 80) ? "B" :
(score >= 70) ? "C" :
(score >= 60) ? "D" : "F";
// Determine pass/fail status
char *status = (score >= 60) ? "PASS" : "FAIL";
// Provide feedback
char *feedback = (score >= 90) ? "Excellent!" :
(score >= 80) ? "Good job!" :
(score >= 70) ? "Satisfactory" :
(score >= 60) ? "Needs improvement" : "Failed";
printf("\nResults:\n");
printf("Score: %d\n", score);
printf("Grade: %s\n", letter_grade);
printf("Status: %s\n", status);
printf("Feedback: %s\n", feedback);
return 0;
}Best Practices
1. Use Parentheses for Clarity
// Good: Clear intent
int result = (a + b) * c;
// Less clear: Relies on precedence knowledge
int result = a + b * c;2. Be Careful with Side Effects
// Dangerous: Undefined behavior
int a = 5;
int result = a++ + ++a;
// Safe: Clear separation of operations
int a = 5;
int temp1 = a++;
int temp2 = ++a;
int result = temp1 + temp2;3. Use Meaningful Variable Names in Complex Expressions
// Good: Self-documenting
int base_salary = 50000;
int bonus_percentage = 10;
int years_of_service = 5;
int total_compensation = base_salary + (base_salary * bonus_percentage / 100) * years_of_service;
// Less clear
int a = 50000;
int b = 10;
int c = 5;
int d = a + (a * b / 100) * c;4. Be Aware of Integer Division
// If you want floating-point result
float average = (float)total / count;
// If you want integer result
int average = total / count;5. Use Bitwise Operations for Bit Manipulation
// Good for setting flags
flags |= FLAG_READ;
// Good for clearing flags
flags &= ~FLAG_WRITE;
// Good for checking flags
if (flags & FLAG_EXECUTE) {
// Execute code
}Summary
In this chapter, you’ve learned about:
- Arithmetic Operators:
+,-,*,/,%for mathematical operations - Relational Operators:
==,!=,>,<,>=,<=for comparisons - Logical Operators:
&&,||,!for boolean logic - Bitwise Operators:
&,|,^,~,<<,>>for bit manipulation - Assignment Operators:
=,+=,-=, etc. for value assignment - Increment/Decrement Operators:
++,--for modifying values by one - Conditional Operator:
?:for ternary selection - Special Operators:
sizeof,&,*for specific operations - Operator Precedence: Rules determining evaluation order
- Best Practices: Writing clear and safe expressions
Understanding operators and expressions is crucial for C programming, as they form the foundation for all program logic. With this knowledge, you’re now ready to tackle more complex programming constructs like control flow statements in the next module.