Operators are the verbs of C: they take values and produce new ones. There are around 40 of them, but a dozen cover most code, and two of those have traps sharp enough to be worth their own sections.
Arithmetic Operators
(%% in the format string prints a literal percent sign - % alone would start a format specifier.)
The five binary arithmetic operators are + - * / %, plus unary - for negation and + which does essentially nothing.
The Integer Division Trap
a / b with two integers performs integer division and throws away the remainder. There is no rounding - the result is truncated toward zero.
That last case is the bug that survives into production. Assigning to a double does not help: the division total / count is already complete, and already an int, before the assignment happens. The fix is to make one operand floating-point inside the expression, usually with a cast.
Two related rules: dividing by zero with integers is undefined behavior and typically crashes the program, and INT_MIN / -1 overflows.
The Modulus Operator
% gives the remainder and works only on integers:
Testing x % 2 == 0 for evenness and using % n to wrap an index around an array are the two everyday uses.
With negative numbers, C's % follows the sign of the dividend:
So x % 2 == 1 is a broken test for oddness when x can be negative - -7 % 2 is -1. Use x % 2 != 0 instead.
For floating-point remainders, % is a compile error; use fmod() from math.h.
Assignment and Compound Assignment
= stores a value. The compound forms combine an operation with the store:
x += 5 means x = x + 5, but it evaluates x only once - which matters when the target is something like arr[compute_index()].
Assignment is itself an expression that yields the assigned value, which is why a = b = c = 0 works (it assigns right to left) and why the = versus == mistake compiles silently.
Increment and Decrement
++ adds one, -- subtracts one. Each comes in a prefix and a postfix form:
As a standalone statement the two are interchangeable, and i++ is the conventional choice in a for header for no deeper reason than tradition.
What you must not do is use a variable more than once in an expression where it is also modified:
int i = 5;
int x = i++ + i++; /* undefined behavior */
arr[i] = i++; /* undefined behavior */
printf("%d %d", i++, i);/* undefined behavior */
C does not define the order in which those subexpressions are evaluated, so the result is not merely unspecified - the whole program's behavior is undefined. Split the statement in two and the ambiguity disappears.
Comparison Operators
Six of them, and all of them produce an int: 1 for true and 0 for false.
The result being a plain int rather than a dedicated boolean type is a defining feature of C - see booleans in C for what follows from it, including the = versus == bug.
Two comparison mistakes are specific to C. Chaining does not work the way mathematics suggests: if (1 < x < 10) is always true, because 1 < x evaluates to 0 or 1 and then that is compared with 10. Write if (x > 1 && x < 10). And comparing strings with == compares pointers, not text - use strcmp from string.h.
Logical Operators
&& is AND, || is OR, ! is NOT. All three treat any non-zero value as true.
They short-circuit: && stops as soon as one operand is false, and || stops as soon as one is true. The rest is never evaluated, which is not just an optimization - it is a guarantee you can rely on for safety:
Swap the order of those two tests and the NULL call dereferences a null pointer and crashes. Guard first, then use - the order in a && chain is part of the logic.
The Conditional Operator
C's only three-operand operator picks between two values:
condition ? value_if_true : value_if_false. It is an expression, so it fits where an if statement cannot - inside a printf argument, in an initializer. Keep it short; nested conditionals become unreadable quickly.
Bitwise Operators, Briefly
These work on the individual bits of an integer:
They are used for flags, masks, and hardware registers. Two cautions: & and | are not && and || (they do not short-circuit and they operate bit by bit), and shifting a signed value or shifting by more than the type's width is undefined. Use unsigned types for bit work.
Precedence
When an expression has no parentheses, this table decides what binds to what. Highest precedence first:
| Level | Operators | Associativity |
|---|---|---|
| 1 | () [] -> . x++ x-- | before to after |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (type) | after to before |
| 3 | * / % | before to after |
| 4 | + - | before to after |
| 5 | << >> | before to after |
| 6 | < <= > >= | before to after |
| 7 | == != | before to after |
| 8 | & | before to after |
| 9 | ^ | before to after |
| 10 | bitwise OR | before to after |
| 11 | && | before to after |
| 12 | logical OR | before to after |
| 13 | ?: | after to before |
| 14 | = += -= *= /= %= and friends | after to before |
| 15 | , | before to after |
The practical consequences:
a + b * c /* means a + (b * c) */
a < b == c < d /* means (a < b) == (c < d) - rarely what you meant */
x & 1 == 0 /* means x & (1 == 0), i.e. x & 0 - a classic bug */
*p++ /* means *(p++) - dereference p, then advance it */
That third line is worth memorizing: == binds tighter than &, so bitmask tests need parentheses - (x & 1) == 0.
Nobody remembers all fifteen levels, and nobody needs to. Learn that * beats +, that comparisons beat && beats ||, and that everything else gets parentheses:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
That line has redundant parentheses and is better for them.
Frequently Asked Questions
What does % do in C?
% is the modulus operator: it gives the remainder of integer division. 17 % 5 is 2. It works only on integers - using it on a float or double is a compile error, and fmod() from math.h is the floating-point equivalent.
Why does 5 / 2 give 2 in C?
Because both operands are integers, so C performs integer division and discards the fraction. Make one side a floating-point value to get 2.5: 5 / 2.0, or cast one operand with (double)a / b.
What is the difference between i++ and ++i?
Both add one to i. ++i (prefix) increments first and yields the new value; i++ (postfix) yields the old value and increments afterwards. As a standalone statement they are identical - the difference only matters when the result is used, as in int b = a++; versus int b = ++a;.
What is operator precedence in C?
The order in which operators bind when an expression has no parentheses. *, /, and % bind tighter than + and -, which bind tighter than comparisons, which bind tighter than &&, then ||, then assignment. When in doubt, add parentheses - they cost nothing and remove the question.