C language includes a wide variety of operators, more than 50 in total, which can be categorized into several types.

Arithmetic Operators

Arithmetic operators are used for mathematical operations. The main ones are as follows:

  • +: Unary plus (positive sign)
  • -: Unary minus (negative sign)
  • +: Addition (binary operator)
  • -: Subtraction (binary operator)
  • *: Multiplication
  • /: Division
  • %: Modulus (remainder of division)
  1. Unary + and -:
    The + and - can act as unary operators, requiring only one operand. Unary - negates a value:
1
int x = -12;

In this example, - changes 12 to -12.

Unary + has no effect and can be omitted without any issue:

1
2
int x = -12;
int y = +x; // y remains -12
  1. Binary + and -:
    These operators are used for addition and subtraction:
1
2
int x = 4 + 22;  // x is 26
int y = 61 - 23; // y is 38
  1. Multiplication *:
    Used for multiplication:
1
2
int num = 5;
printf("%i\n", num * num); // Outputs 25
  1. Division /:
    Used for division. Note that division between two integers results in an integer:
1
2
float x = 6 / 4;
printf("%f\n", x); // Outputs 1.000000

In this example, even though x is of type float, the division 6 / 4 results in 1.0, not 1.5. This is because integer division in C truncates the decimal part, returning only the integer portion.

To get a floating-point result, at least one operand must be a float. For example:

1
2
float x = 6.0 / 4;  // Or 6 / 4.0
printf("%f\n", x); // Outputs 1.500000

Here, 6.0 / 4 performs floating-point division, yielding 1.5.

Consider this example:

1
2
int score = 5;
score = (score / 20) * 100;

You might expect score to be 25, but it will actually be 0. This is because score / 20 performs integer division, resulting in 0, and multiplying this by 100 also gives 0.

To get the expected result, change 20 to 20.0 to use floating-point division:

1
score = (score / 20.0) * 100;
  1. Modulus %:
    The % operator represents the modulo operation, which returns the remainder of dividing two integers. This operator can only be used with integers, not floating-point numbers. For example:
1
int x = 6 % 4; // Result is 2

The rules for modulo with negative numbers dictate that the sign of the result is determined by the sign of the first operand(the sign of the result follows the first operand):

1
2
3
11 % -5  // Result is 1
-11 % -5 // Result is -1
-11 % 5 // Result is -1

In these examples, the sign of the result (whether it is positive or negative) matches the sign of the first operand (11 or -11).

  1. Compound Assignment Operators

C provides shorthand forms for performing arithmetic operations on a variable and assigning the result back to the same variable. These compound assignment operators combine the assignment operator with an arithmetic operator:

  • +=
  • -=
  • *=
  • /=
  • %=

Here are some examples:

1
2
3
4
5
i += 3;  // Equivalent to i = i + 3
i -= 8; // Equivalent to i = i - 8
i *= 9; // Equivalent to i = i * 9
i /= 2; // Equivalent to i = i / 2
i %= 5; // Equivalent to i = i % 5

These shorthand notations make code more concise and readable.

Increment and Decrement Operators

C has two operators to increase or decrease a variable by 1:

  • ++: Increment operator
  • --: Decrement operator

Example:

1
2
3
int i = 42;
i++; // Equivalent to i = i + 1
i--; // Equivalent to i = i - 1

The position of these operators affects their behavior. If placed before a variable (++i), it increments first and then returns the value. If placed after (i++), it returns the current value before incrementing.

Consider this example:

1
2
3
4
5
6
7
8
9
10
int i = 42;
int j;

j = (i++ + 10);
// i: 43
// j: 52

j = (++i + 10);
// i: 44
// j: 54

The position of the increment operator affects the value assigned to j. To avoid confusion and improve code clarity, separate the increment from the expression:

1
2
3
4
5
6
7
/* Option 1 */
j = (i + 10);
i++;

/* Option 2 */
i++;
j = (i + 10);

This way, the increment operation and the value assignment are distinct, reducing potential errors and enhancing readability.

Relational Operators

In C, relational operators are used to compare values and are crucial for conditional statements. Here are the six primary relational operators:

  • > : Greater than
  • < : Less than
  • >= : Greater than or equal to
  • <= : Less than or equal to
  • == : Equal to
  • != : Not equal to

Examples:

1
2
3
4
5
6
a == b;  // Checks if a is equal to b
a != b; // Checks if a is not equal to b
a < b; // Checks if a is less than b
a > b; // Checks if a is greater than b
a <= b; // Checks if a is less than or equal to b
a >= b; // Checks if a is greater than or equal to b

Relational expressions return 0 (false) or 1 (true). For instance, 20 > 12 returns 1, while 12 > 20 returns 0.

These expressions are commonly used in if or while statements:

1
2
3
if (x == 3) {
printf("x is 3.\n");
}

Note: The equality operator == is different from the assignment operator =. Avoid mistakes like this:

1
if (x = 3) ...

This assigns 3 to x and always evaluates to true. Instead, write:

1
if (3 == x) ...

This way, if you mistakenly use = instead of ==, the compiler will catch the error.

Also, avoid chaining relational operators like this:

1
i < j < k

This does not check if j is between i and k. Instead, use:

1
i < j && j < k

Logical Operators

Logical operators are used for logical comparisons and constructing more complex expressions. Here are the three main logical operators:

  • ! : Logical NOT (reverses the truth value of a single expression).
  • && : Logical AND (true if both expressions are true, otherwise false).
  • || : Logical OR (true if at least one of the expressions is true, otherwise false).

For example with the AND operator:

1
2
if (x < 10 && y > 20)
printf("Doing something!\n");

In this case, x < 10 && y > 20 is true only if both x < 10 and y > 20 are true.

For the NOT operator:

1
2
if (!(x < 12))
printf("x is not less than 12\n");

Here, ! has higher precedence than <, so parentheses are needed to negate x < 12. A more straightforward way to write this would be if (x >= 12).

Logical operators treat any non-zero value as true and zero as false. For instance, 5 || 0 returns 1, and 5 && 0 returns 0.

A key feature of logical operators is short-circuit evaluation: the left expression is evaluated first, and if it determines the result, the right expression is not evaluated. For example:

1
if (number != 0 && 12/number == 2)

If number is 0, 12/number will not be evaluated, preventing a divide-by-zero error.

When using logical operators, be cautious of changing variables within the expressions:

1
while ((x++ < 10) && (x + y < 20))

Here, x is incremented in the left expression, potentially affecting the right expression in unexpected ways.

Bitwise Operators

C provides several bitwise operators to manipulate binary digits (bits). Here’s a rundown of each:

Bitwise NOT Operator (~)

The bitwise NOT operator (~) is a unary operator that inverts each bit of its operand, changing 0s to 1s and 1s to 0s.

1
2
// Returns 01101100
~10010011

In the example above, applying ~ to each bit of 10010011 results in a new value. Note that the ~ operator does not alter the original value but returns a new one.

Bitwise AND Operator (&)

The bitwise AND operator (&) compares each bit of two operands. It returns 1 if both bits are 1; otherwise, it returns 0.

1
2
// Returns 00010001
10010011 & 00111101

Here, 10010011 and 00111101 are compared bit by bit, resulting in a new value. The & operator can be combined with the assignment operator =, written as &=.

1
2
3
4
5
int val = 3;
val = val & 0377;

// Shorthand
val &= 0377;

Bitwise OR Operator (|)

The bitwise OR operator (|) compares each bit of two operands and returns 1 if at least one of the bits is 1. If both bits are 0, it returns 0.

1
2
// Returns 10111111
10010011 | 00111101

In this example, 10010011 and 00111101 are compared bit by bit, resulting in a new value. The | operator can also be combined with the assignment operator =, written as |=.

1
2
3
4
5
int val = 3;
val = val | 0377;

// Shorthand
val |= 0377;

Bitwise XOR Operator (^)

The bitwise XOR operator (^) compares each bit of two operands. It returns 1 if only one of the bits is 1; if both are 0 or both are 1, it returns 0.

1
2
// Returns 10101110
10010011 ^ 00111101

The ^ operator can be combined with the assignment operator =, written as ^=.

1
2
3
4
5
int val = 3;
val = val ^ 0377;

// Shorthand
val ^= 0377;

Left Shift Operator (<<)

The left shift operator (<<) shifts the bits of its left operand to the left by a specified number of positions. The empty positions on the right are filled with 0s.

1
2
// Returns 1000101000
10001010 << 2

Here, each bit in 10001010 is shifted two positions to the left. The left shift operator is equivalent to multiplying the operand by 2 raised to the specified power (e.g., shifting left by 2 positions is the same as multiplying by 4).

The left shift operator can be combined with the assignment operator =, written as <<=.

1
2
3
4
5
int val = 1;
val = val << 2;

// Shorthand
val <<= 2;

Right Shift Operator (>>)

The right shift operator (>>) shifts the bits of its left operand to the right by a specified number of positions. The bits that fall off are discarded, and the empty positions on the left are filled with 0s.

1
2
// Returns 00100010
10001010 >> 2

In this example, each bit in 10001010 is shifted two positions to the right. The two lowest bits (10) are discarded, and the leftmost bits are filled with 0s. Note that right shifting is best used with unsigned integers to avoid issues with how different systems handle the sign bit.

The right shift operator is equivalent to dividing the operand by 2 raised to the specified power (e.g., shifting right by 2 positions is the same as dividing by 4).

The right shift operator can be combined with the assignment operator =, written as >>=.

1
2
3
4
5
int val = 1;
val = val >> 2;

// Shorthand
val >>= 2;

Comma Operator

The comma operator allows multiple expressions to be written in a single statement, executing them from left to right.

1
x = 10, y = 20;

In this example, the comma operator lets you include both x = 10 and y = 20 in one statement.

The comma operator returns the value of the last expression, which is the value of the whole statement.

1
2
int x;
x = (1, 2, 3);

Here, the comma operator inside the parentheses results in x being assigned the value 3, the last expression.

Operator Precedence

Operator precedence determines the order of operations in expressions with multiple operators. Different operators have different precedence levels.

1
3 + 4 * 5;

In this expression, multiplication (*) has higher precedence than addition (+), so 4 * 5 is calculated first.

If operators have the same precedence, their associativity decides the order. Most operators are left-associative (evaluated from left to right), while a few, like the assignment operator (=), are right-associative (evaluated from right to left).

1
5 * 6 / 2;

Since both * and / have the same precedence and are left-associative, the expression is evaluated as 5 * 6 first, then 30 / 2.

The precedence of operators is complex. Here’s a simplified list from highest to lowest:

  1. Parentheses ()
  2. Increment (++), Decrement (--)
  3. Unary operators (+, -)
  4. Multiplication (*), Division (/)
  5. Addition (+), Subtraction (-)
  6. Relational operators (<, >, etc.)
  7. Assignment operators (=)

Using parentheses can override default precedence and improve code clarity:

1
int x = (3 + 4) * 5;

In this case, the parentheses ensure that addition is performed before multiplication.

Remembering all operator precedences is unnecessary. Use parentheses to clarify and ensure your code behaves as expected.