C Programming C++ Java JavaScript Kotlin PHP Python

πŸ’» C Programming

Learn the basics of C programming, including variables, loops, and functions.

Operators & Expressions::(Arithmetic Operators, Relational Operators, Logical Operators, Assignment Operators, Increment & Decrement)

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 % b has same sign as a)

Examples:

int a = 7, b = 3; int s = a + b; // 10 int d = a / b; // 2 (integer division) int r = a % b; // 1 (remainder) double x = 7.0 / 3.0; // 2.333333...

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:

int a = 5, b = 7; if (a < b) // true (1) puts("a < b"); int eq = (a == b); // 0 (false)

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:

if (x > 0 && x < 100) // both true do_something(); if (ptr != NULL && ptr->value == 0) // safe: second operand evaluated only if ptr != NULL ...

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:

int a = 5; a += 3; // a = a + 3 => 8 a *= 2; // a = a * 2 => 16

Important points & pitfalls:

  • Assignment = returns the assigned value (so you can write if ((x = f()) != 0)).

  • Beware using = inside if by accident instead of ==. Many style guides recommend writing comparisons as if (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:

int x = 5; int a = ++x; // x becomes 6, a = 6 x = 5; int b = x++; // b = 5, then x becomes 6

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:

unsigned int x = 0x0F; // 00001111 unsigned int y = x << 2; // 00111100 => 0x3C unsigned int z = x & 0x03; // 00000011 => 3 unsigned int b = x ^ 0xFF; // invert selected bits

Shifts specifics:

  • x << n: shifts bits left by n places (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:

int a = 10, b = 20; int max = (a > b) ? a : b;

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 : e is a ? 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):

postfix () [] . -> ++ -- (highest) unary + - ! ~ ++ -- (type) * & sizeof * / % + - << >> < <= > >= == != & ^ | && || ?: (ternary) = += -= ... , (lowest)

Examples to illustrate precedence:

int x = 2 + 3 * 4; // 2 + (3*4) = 14, not (2+3)*4 int y = a << 1 + 2; // a << (1+2) because + has lower precedence than <<? actually + has higher than << so 1+2 evaluated first: correct: a << (1+2)

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 to long double.

  • Else if either is double β†’ convert other to double.

  • Else if either is float β†’ convert other to float.

  • 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:

int i = 3; double d = 2.5; double r = i + d; // i promoted to double -> 5.5

Explicit casting:

int i = (int)3.7; // 3 (fraction truncated) double d = (double)5; // 5.0

Common gotchas & pitfalls:

  • Lossy conversions: casting double β†’ int truncates fractional part.

  • Signed/unsigned promotions: mixing signed negative numbers with unsigned types can yield large positive values after conversion.

    int a = -1; unsigned int b = 1u; if (a < b) { /* surprising: a converted to unsigned -> huge -> false */ }
  • Integer division before casting: double r = i / j; if both i and j are ints, division is integer and fractional part lost. Use double r = (double)i / j;

  • Overflow on conversion: converting large long long to int may overflow (implementation-defined or UB depending on signedness).

Best practices:

  • Prefer to perform arithmetic in the widest needed type (e.g., in double for 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

int a = 5, b = 2; double d = 3.0; double res = a + b * d / (a - b); // follows precedence: b*d -> / -> + -> parentheses as shown

Evaluation steps (textual):

  1. b * d (multiplicative) β†’ 2 * 3.0 = 6.0 (int promoted to double)

  2. 6.0 / (a - b) β†’ a - b = 3 β†’ 6.0 / 3 = 2.0

  3. a + 2.0 β†’ 5 + 2.0 = 7.0

Example 2: Beware signed/unsigned

int x = -1; unsigned int y = 1U; if (x < y) // x converted to unsigned -> large -> condition false puts("x < y"); else puts("x >= y"); // printed

Example 3: Bitwise flags

#define FLAG_A 0x01 #define FLAG_B 0x02 unsigned int flags = 0; flags |= FLAG_A; // set A if (flags & FLAG_A) { ... } // test A flags &= ~FLAG_A; // clear A

Quick reference: Best Practices & Rules of Thumb

  1. Use parentheses to make complex expressions explicit.

  2. Avoid mixing signed and unsigned β€” cast deliberately when needed.

  3. Check for overflow (signed overflow is UB). Use wider types or unsigned if wrap-around is desired.

  4. Prefer clear, single-purpose expressions rather than clever chained expressions with side-effects.

  5. Use &&/|| short-circuit to guard against null pointers or expensive operations.

  6. Use unsigned types for bitwise and shift operations to avoid implementation-defined sign behavior.

  7. Initialize variables before use; do not rely on default values.

  8. When in doubt, split expressions into multiple statements with meaningful variable names β€” it’s easier to read and debug.

  9. Enable compiler warnings (-Wall -Wextra) to catch suspicious mixes and implicit conversions.

  10. Document casts when you must convert types to make intent clear.

❓

No quizzes yet

Quizzes to test your knowledge coming soon!

πŸ“š Recent Tutorials

Explore the latest tutorials added to our platform

Code copied to clipboard!