An expression in C is any combination of variables, constants, function calls, and operators that evaluates to a value.
An operator performs a specific operation (arithmetic, logical, bitwise, etc.) on one or more operands.
Expressions are the building blocks of programs: assignments, computations, conditions, and function calls are all expressions.
1. Arithmetic Operators
Purpose: perform numeric calculations on integer and floating-point operands.
Operators: + - * / %
-
+addition -
-subtraction (binary), negation (unary) -
*multiplication -
/division (integer division truncates toward zero for integers) -
%remainder (modulo) β defined only for integer types (result sign rules: in C99 onward,a % bhas same sign asa)
Examples:
Important points & pitfalls:
-
Integer division discards fractional part. Use floating types for fractional results.
-
Division by zero is undefined behavior (UB) for integers and runtime error for floats (usually Β±Inf or NaN).
-
Beware overflow: signed integer overflow is UB; unsigned wraps modulo 2^n.
-
Use parentheses to clarify intent and control order:
(a + b) / c.
Best practice: Use double for general-purpose floating calculations; check for possible divide-by-zero and range/overflow.
2.Relational Operators
Purpose: compare values; result is 1 (true) or 0 (false) in C.
Operators: == != > < >= <=
Examples:
Important points & pitfalls:
-
Use
==for comparison, not=(assignment). A common bug:if (x = 0)assigns, then tests. -
When comparing floats, avoid exact equality due to rounding β use tolerance:
fabs(a - b) < EPS. -
Be mindful of signed/unsigned mixing: comparing signed negative to unsigned causes conversion to unsigned (surprising results).
Best practice: Prefer explicit casts when mixing signed and unsigned in comparisons.
3. Logical Operators
Purpose: combine boolean conditions.
Operators: ! && ||
-
!logical NOT (unary) -
&&logical AND (short-circuit) -
||logical OR (short-circuit)
Examples:
Short-circuit behavior:
-
For
&&, if left operand is false (0), right operand is not evaluated. -
For
||, if left operand is true (non-zero), right operand is not evaluated.
Important points & pitfalls:
-
Use short-circuit to guard operations (e.g.,
ptr && ptr->field). -
Avoid using
&&/||for producing side effects that must always run. -
In C, any nonzero integer is considered true.
Best practice: Use parentheses to group complex logical expressions and write clear guard conditions.
4. Assignment Operators
Purpose: assign values; also combine assignment with arithmetic/bitwise operations.
Operators: = += -= *= /= %= <<= >>= &= ^= |=
Examples:
Important points & pitfalls:
-
Assignment
=returns the assigned value (so you can writeif ((x = f()) != 0)). -
Beware using
=insideifby accident instead of==. Many style guides recommend writing comparisons asif (42 == x)so accidental=will be a compile error. -
For compound assignment, the right-hand side is evaluated once, then combined.
Best practice: Prefer clarity; use compound assignments for concise code, but beware of complex expressions with side effects.
5. Increment & Decrement
Operators: ++ (increment), -- (decrement). Both have prefix and postfix forms.
-
++x(prefix): increment x, then evaluate to new value -
x++(postfix): evaluate to old value, then increment x
Examples:
Important points & pitfalls:
-
Do not use
++/--on the same variable more than once in an expression (e.g.,i = i++ + ++i;) β leads to undefined behavior. -
Prefix form is sometimes more efficient for complex types (e.g., iterators in C++), but in C for integers either is fine when used alone.
Best practice: Use increment/decrement as standalone statements (i++;) for clarity.
6. Bitwise Operators
Purpose: operate at the binary (bit) level on integer operands.
Operators: & | ^ ~ << >>
-
&bitwise AND -
|bitwise OR -
^bitwise XOR (exclusive OR) -
~bitwise NOT (unary) -
<<left shift -
>>right shift
Examples:
Shifts specifics:
-
x << n: shifts bits left bynplaces (zero-fill on right). Shifting by >= width is undefined. -
x >> n: for unsigned, does logical shift (zero-fill); for signed, implementation-defined (commonly arithmetic shift: sign bit replicated). Prefer shifting unsigned values for predictable behavior.
Use-cases:
-
Bit masks (flags), packing/unpacking fields, fast multiplication/division by powers of two, embedded register manipulation, cryptography.
Best practice & pitfalls:
-
Operate on unsigned types for shifts and bit-twiddling to avoid sign issues.
-
Mask values when extracting bits:
(value >> offset) & mask. -
Avoid undefined behavior: do not shift by a negative count or by a count >= number of bits in the type.
7. Conditional (Ternary) Operator
Operator: ?: β a compact conditional expression.
Syntax: condition ? expr_if_true : expr_if_false
Example:
Important points & pitfalls:
-
The operator yields a value, not a statement. Both branches should be of compatible types or will be converted.
-
Avoid using it for complex side-effect expressions; prefers single concise expressions.
-
Ternary operator is right-associative:
a ? b : c ? d : eisa ? b : (c ? d : e).
Best practice: Use for short conditional expressions; prefer if for more complex logic.
8. Operator Precedence & Associativity
Purpose: determine the order in which sub-expressions are evaluated when multiple operators appear without parentheses.
High-level precedence table (from highest to lowest, showing the most important categories).
Put parentheses to override and to improve readability.
-
Postfix:
() [] -> . ++ --β highest precedence, 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:
= += -= *= /= %= <<= >>= &= ^= |=β right-to-left -
Comma:
,β left-to-right (lowest)
Diagram (textual):
Examples to illustrate precedence:
Associativity: tells how operators of the same precedence group are grouped: left-to-right or right-to-left. Example: a - b - c is (a - b) - c (left-to-right). Assignment is right-to-left: a = b = c is a = (b = c).
Best practice: Use parentheses liberally to document intent, even when precedence yields the same result.
9.Type Conversion & Casting
Purpose: when operators combine operands of different types, C converts them to a common type according to the usual arithmetic conversions and integer promotions.
Integer promotions
Small integer types (char, short) are promoted to int (or unsigned int if int cannot represent all values) before arithmetic.
Usual arithmetic conversions
-
If either operand is
long doubleβ convert the other tolong double. -
Else if either is
doubleβ convert other todouble. -
Else if either is
floatβ convert other tofloat. -
Else perform integer promotions, then if one type is unsigned with higher rank, convert the other to that unsigned type, etc. (rules are a bit detailed β general idea: convert to the wider type; unsigned may win).
Examples:
Explicit casting:
Common gotchas & pitfalls:
-
Lossy conversions: casting
doubleβinttruncates fractional part. -
Signed/unsigned promotions: mixing signed negative numbers with unsigned types can yield large positive values after conversion.
-
Integer division before casting:
double r = i / j;if bothiandjare ints, division is integer and fractional part lost. Usedouble r = (double)i / j; -
Overflow on conversion: converting large
long longtointmay overflow (implementation-defined or UB depending on signedness).
Best practices:
-
Prefer to perform arithmetic in the widest needed type (e.g., in
doublefor real math). -
Use explicit casts to document intent, but avoid cast to silence warnings without understanding the consequence.
-
For portability and clarity, use fixed-width types (
int32_t,uint64_t) when binary representation or precise ranges matter. -
When mixing signed/unsigned, either use all signed or all unsigned, or cast explicitly with caution.
Putting it all together: Expression examples & evaluation
Example 1: Combined expression
Evaluation steps (textual):
-
b * d(multiplicative) β2 * 3.0=6.0(int promoted to double) -
6.0 / (a - b)βa - b = 3β6.0 / 3=2.0 -
a + 2.0β5 + 2.0=7.0
Example 2: Beware signed/unsigned
Example 3: Bitwise flags
Quick reference: Best Practices & Rules of Thumb
-
Use parentheses to make complex expressions explicit.
-
Avoid mixing signed and unsigned β cast deliberately when needed.
-
Check for overflow (signed overflow is UB). Use wider types or unsigned if wrap-around is desired.
-
Prefer clear, single-purpose expressions rather than clever chained expressions with side-effects.
-
Use
&&/||short-circuit to guard against null pointers or expensive operations. -
Use unsigned types for bitwise and shift operations to avoid implementation-defined sign behavior.
-
Initialize variables before use; do not rely on default values.
-
When in doubt, split expressions into multiple statements with meaningful variable names β itβs easier to read and debug.
-
Enable compiler warnings (
-Wall -Wextra) to catch suspicious mixes and implicit conversions. -
Document casts when you must convert types to make intent clear.