Skip to main content
C beginner Lesson 5 of 23

Operators in C

Master arithmetic, bitwise, shift, and logical operators in C, including operator precedence and the comma operator.

Arithmetic Operators

Arithmetic operators perform mathematical calculations on numeric values. The most important subtlety in C is that integer division truncates toward zero — 7 / 2 gives 3, not 3.5. This is a frequent source of bugs when developers expect floating-point behavior.

#include <stdio.h>

int main(void) {
    int a = 17, b = 5;

    printf("Addition:       %d + %d = %d\n",  a, b, a + b);   /* 22 */
    printf("Subtraction:    %d - %d = %d\n",  a, b, a - b);   /* 12 */
    printf("Multiplication: %d * %d = %d\n",  a, b, a * b);   /* 85 */
    printf("Division:       %d / %d = %d\n",  a, b, a / b);   /* 3  — truncates */
    printf("Modulo:         %d %% %d = %d\n", a, b, a % b);   /* 2  */

    /* Cast one operand to double to get floating-point division */
    double result = (double)a / b;
    printf("Float division: %d / %d = %.4f\n", a, b, result); /* 3.4000 */

    return 0;
}

Increment and Decrement

Prefix (++x) increments before returning the value; postfix (x++) returns the value then increments. The difference only matters when the result is used in a larger expression.

int x = 5;

/* Prefix: increment first, then use the value */
int a = ++x;   /* x becomes 6, a = 6 */

/* Postfix: use the value first, then increment */
int b = x++;   /* b = 6, then x becomes 7 */

printf("x=%d, a=%d, b=%d\n", x, a, b);  /* x=7, a=6, b=6 */

Compound Assignment

Compound assignment operators combine an arithmetic operation with assignment. They are shorter to write and make intent clearer.

int n = 10;
n += 5;   /* n = 15 */
n -= 3;   /* n = 12 */
n *= 2;   /* n = 24 */
n /= 4;   /* n = 6  */
n %= 4;   /* n = 2  */

Relational and Logical Operators

Relational operators compare two values and return 1 (true) or 0 (false). Logical operators combine boolean conditions and support short-circuit evaluation — the second operand is not evaluated if the first determines the result.

#include <stdio.h>

int main(void) {
    int a = 5, b = 10;

    /* Relational — return 1 (true) or 0 (false) */
    printf("%d\n", a == b);  /* 0 */
    printf("%d\n", a != b);  /* 1 */
    printf("%d\n", a <  b);  /* 1 */
    printf("%d\n", a >  b);  /* 0 */
    printf("%d\n", a <= b);  /* 1 */
    printf("%d\n", a >= b);  /* 0 */

    /* Short-circuit: second operand not evaluated if first decides the result */
    int x = 0;
    if (x != 0 && 10 / x > 1) {  /* safe: division not reached if x==0 */
        printf("unreachable\n");
    }

    int y = 1;
    if (y == 1 || expensive_function()) {  /* expensive_function() never called */
        printf("short-circuit\n");
    }

    /* Logical NOT */
    printf("%d\n", !0);   /* 1 — zero is "false" */
    printf("%d\n", !5);   /* 0 — any non-zero value is "true" */

    return 0;
}

Bitwise Operators

Bitwise operators work on the individual bits of integer values. They are fundamental to systems and embedded programming: setting and clearing hardware flags, packing multiple values into a single integer, and implementing efficient algorithms.

#include <stdio.h>

int main(void) {
    unsigned char a = 0b10110100;  /* 180 */
    unsigned char b = 0b11001010;  /* 202 */

    printf("a          = %08b (%3d)\n", a, a);
    printf("b          = %08b (%3d)\n", b, b);
    printf("a & b      = %08b (%3d)\n", a & b,  a & b);  /* AND  — both bits 1 */
    printf("a | b      = %08b (%3d)\n", a | b,  a | b);  /* OR   — either bit 1 */
    printf("a ^ b      = %08b (%3d)\n", a ^ b,  a ^ b);  /* XOR  — bits differ */
    printf("~a         = %08b (%3d)\n", (unsigned char)~a, (unsigned char)~a); /* NOT — flip all bits */
    printf("a << 2     = %08b (%3d)\n", (unsigned char)(a << 2), (unsigned char)(a << 2)); /* left shift  */
    printf("a >> 2     = %08b (%3d)\n", a >> 2, a >> 2); /* right shift */

    return 0;
}

Practical Bitwise Patterns

These four patterns appear constantly in systems code for working with flags and hardware registers:

Set a bit (turn bit N on):

flags |= (1 << N);

Clear a bit (turn bit N off):

flags &= ~(1 << N);

Toggle a bit (flip bit N):

flags ^= (1 << N);

Test a bit (check if bit N is set):

if (flags & (1 << N)) { /* bit N is set */ }

Real-world example — working with hardware register flags:

#include <stdint.h>

/* Define named constants for each bit position */
#define STATUS_READY   (1 << 0)   /* bit 0 */
#define STATUS_BUSY    (1 << 1)   /* bit 1 */
#define STATUS_ERROR   (1 << 2)   /* bit 2 */
#define STATUS_TIMEOUT (1 << 3)   /* bit 3 */

void process_status(uint8_t status) {
    if (status & STATUS_ERROR) {
        /* handle error */
        status &= ~STATUS_ERROR;   /* clear error flag */
    }
    if (status & STATUS_READY) {
        /* device is ready — mark it as busy */
        status |= STATUS_BUSY;
    }
}

Shift Operators

Shift operators move bits left or right within an integer. Left shifting by N is equivalent to multiplying by 2^N; right shifting by N is equivalent to dividing by 2^N (for unsigned values). They are faster than multiplication or division on most hardware.

#include <stdio.h>

int main(void) {
    unsigned int x = 1;

    /* Left shift: multiply by 2^n */
    printf("%u\n", x << 1);   /* 2  */
    printf("%u\n", x << 4);   /* 16 */
    printf("%u\n", x << 8);   /* 256 */

    unsigned int y = 256;

    /* Right shift: divide by 2^n (fills with 0 for unsigned) */
    printf("%u\n", y >> 1);   /* 128 */
    printf("%u\n", y >> 4);   /* 16  */
    printf("%u\n", y >> 8);   /* 1   */

    return 0;
}

Warning: Right-shifting a signed negative integer is implementation-defined. For bit manipulation, always use unsigned types to get predictable behavior.

The Ternary Operator

The ternary operator ? : is a compact conditional expression — it evaluates a condition and returns one of two values. It’s useful for simple conditional assignments but should be avoided for complex logic where a full if/else is clearer.

int max(int a, int b) {
    return (a > b) ? a : b;  /* returns a if a > b, otherwise b */
}

/* Equivalent to: */
int max2(int a, int b) {
    if (a > b) return a;
    else       return b;
}

/* Nested ternary — use sparingly, hurts readability */
const char *classify(int n) {
    return (n > 0) ? "positive" : (n < 0) ? "negative" : "zero";
}

The Comma Operator

The comma operator evaluates both operands left to right, discards the left result, and returns the right result. Its most common legitimate use is initializing multiple variables in a for loop header.

/* Most common use: multiple variables in for loop */
for (int i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

/* Less common: force multiple expressions where one is expected */
int x = (printf("side effect\n"), 42);  /* x = 42, but printf ran first */

Operator Precedence

Higher precedence operators bind more tightly. Precedence bugs are common and subtle — when in doubt, use parentheses to make your intent explicit rather than relying on memorized rules.

PrecedenceOperatorsAssociativity
1 (highest)() [] -> .Left to right
2! ~ ++ -- + - * & sizeof (unary)Right to left
3* / %Left to right
4+ -Left to right
5<< >>Left to right
6< <= > >=Left to right
7== !=Left to right
8&Left to right
9^Left to right
10|Left to right
11&&Left to right
12||Left to right
13?:Right to left
14= += -= etc.Right to left
15 (lowest),Left to right

Common precedence traps:

int x = 2 + 3 * 4;      /* 14, not 20 — * binds tighter than + */
int y = 1 << 2 + 1;     /* 1 << 3 = 8, not (1 << 2) + 1 = 5 */
int z = ~0 & 0xFF;       /* (~0) & 0xFF = 255 */

/* When in doubt, parenthesize to make intent explicit */
int a = (2 + 3) * 4;    /* 20 */
int b = 1 << (2 + 1);   /* 8, and intent is crystal clear */

Frequently Asked Questions

What is the difference between & and &&?
& is the bitwise AND operator — it operates on individual bits. && is the logical AND operator — it evaluates boolean conditions and short-circuits (stops evaluating if the first operand is false).
What does the % operator do with negative numbers?
In C99 and later, the result of a%b has the same sign as a. So -7 % 3 == -1, not 2. This differs from Python where the result takes the sign of the divisor.
Why should I know bitwise operators?
Bitwise operations are essential for systems programming: setting/clearing hardware flags, packing data efficiently, writing network code, implementing cryptography, and optimizing performance-critical loops.